/*
* Copyright (C) 2013,2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Authored by: Daniel d'Andrada
*/
/* Some parts of the code were copied from the XCB platform plugin of the Qt Toolkit,
* which is under the following license:
*/
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the .
**
** $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 "MouseTouchAdaptor.h"
#include
#include
#include
#include
#include
#include
#include
using QTest::QTouchEventSequence;
namespace {
MouseTouchAdaptor *g_instance = nullptr;
const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
const Qt::KeyboardModifiers QUAD_PRESS_MODIFIER = TRI_PRESS_MODIFIER|Qt::MetaModifier;
Qt::MouseButton translateMouseButton(xcb_button_t detail)
{
switch (detail) {
case 1: return Qt::LeftButton;
case 2: return Qt::MidButton;
case 3: return Qt::RightButton;
// Button values 4-7 are Wheel events
default: return Qt::NoButton;
}
}
Qt::KeyboardModifiers translateMofidier(uint32_t mod)
{
Qt::KeyboardModifiers qtMod = Qt::NoModifier;
if (mod & 0x01) qtMod |= Qt::ShiftModifier;
if (mod & 0x04) qtMod |= Qt::ControlModifier;
if (mod & 0x08) qtMod |= Qt::AltModifier;
if (mod & 0x40) qtMod |= Qt::MetaModifier;
return qtMod;
}
} // end of anonymous namespace
MouseTouchAdaptor::MouseTouchAdaptor()
: QObject(nullptr)
, m_leftButtonIsPressed(false)
, m_triPressModifier(false)
, m_quadPressModifier(false)
, m_enabled(true)
{
QCoreApplication::instance()->installNativeEventFilter(this);
m_touchDevice = new QTouchDevice;
m_touchDevice->setType(QTouchDevice::TouchScreen);
QWindowSystemInterface::registerTouchDevice(m_touchDevice);
fetchXInput2Info();
}
MouseTouchAdaptor::~MouseTouchAdaptor()
{
g_instance = nullptr;
}
MouseTouchAdaptor* MouseTouchAdaptor::instance()
{
if (!g_instance) {
g_instance = new MouseTouchAdaptor;
}
return g_instance;
}
void MouseTouchAdaptor::fetchXInput2Info()
{
QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
Display *xDisplay = static_cast(nativeInterface->nativeResourceForIntegration("Display"));
if (xDisplay && XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
int xiMajor = 2;
m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
m_xi2Minor = 1; // for smooth scrolling 2.1 is enough
if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
m_xi2Minor = 0; // for tablet support 2.0 is enough
m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
} else
m_xi2Enabled = true;
} else {
m_xi2Enabled = true;
}
}
}
// Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed:
// - "pad0" became "extension"
// - "pad1" and "pad" became "pad0"
// New and old version of this struct share the following fields:
// NOTE: API might change again in the next release of xcb in which case this comment will
// need to be updated to reflect the reality.
typedef struct qt_xcb_ge_event_t {
uint8_t response_type;
uint8_t extension;
uint16_t sequence;
uint32_t length;
uint16_t event_type;
} qt_xcb_ge_event_t;
bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode)
{
qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev;
// xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from
// the xcb version 1.9.3, prior to that it was called "pad0".
if (event->extension == opCode) {
// 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);
return true;
}
return false;
}
static inline qreal fixed1616ToReal(FP1616 val)
{
return qreal(val) / 0x10000;
}
bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
{
if (!xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) {
return false;
}
xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event);
xXIDeviceEvent *xiDeviceEvent = 0;
switch (xiEvent->evtype) {
case XI_ButtonPress:
case XI_ButtonRelease:
case XI_Motion:
xiDeviceEvent = reinterpret_cast(event);
break;
default:
break;
}
if (!xiDeviceEvent) {
return false;
}
switch (xiDeviceEvent->evtype) {
case XI_ButtonPress:
return handleButtonPress(
static_cast(xiDeviceEvent->event),
xiDeviceEvent->detail,
xiDeviceEvent->mods.base_mods,
fixed1616ToReal(xiDeviceEvent->event_x),
fixed1616ToReal(xiDeviceEvent->event_y));
case XI_ButtonRelease:
return handleButtonRelease(
static_cast(xiDeviceEvent->event),
xiDeviceEvent->detail,
xiDeviceEvent->mods.base_mods,
fixed1616ToReal(xiDeviceEvent->event_x),
fixed1616ToReal(xiDeviceEvent->event_y));
case XI_Motion:
return handleMotionNotify(
static_cast(xiDeviceEvent->event),
xiDeviceEvent->mods.base_mods,
fixed1616ToReal(xiDeviceEvent->event_x),
fixed1616ToReal(xiDeviceEvent->event_y));
return true;
default:
return false;
}
}
bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
void * message, long * /*result*/)
{
static int eventCount = 0;
eventCount++;
if (!m_enabled) {
return false;
}
if (eventType != "xcb_generic_event_t") {
// wrong backend.
qWarning("MouseTouchAdaptor: XCB backend not in use. Adaptor inoperative!");
return false;
}
xcb_generic_event_t *xcbEvent = static_cast(message);
switch (xcbEvent->response_type & ~0x80) {
case XCB_BUTTON_PRESS: {
auto pressEvent = reinterpret_cast(xcbEvent);
return handleButtonPress(static_cast(pressEvent->event), pressEvent->detail, 0,
pressEvent->event_x, pressEvent->event_y);
}
case XCB_BUTTON_RELEASE: {
auto releaseEvent = reinterpret_cast(xcbEvent);
return handleButtonRelease(static_cast(releaseEvent->event), releaseEvent->detail, 0,
releaseEvent->event_x, releaseEvent->event_y);
}
case XCB_MOTION_NOTIFY: {
auto motionEvent = reinterpret_cast(xcbEvent);
return handleMotionNotify(static_cast(motionEvent->event), 0,
motionEvent->event_x, motionEvent->event_y);
}
case XCB_GE_GENERIC:
if (m_xi2Enabled) {
return xi2HandleEvent(reinterpret_cast(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 = translateMouseButton(detail);
Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
// Just eat the event if it wasn't a left mouse press
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);
if (qtMod == TRI_PRESS_MODIFIER) {
touchEvent.press(1, windowPos);
touchEvent.press(2, windowPos);
m_triPressModifier = true;
}
if (qtMod == QUAD_PRESS_MODIFIER) {
touchEvent.press(1, windowPos);
touchEvent.press(2, windowPos);
touchEvent.press(3, windowPos);
m_quadPressModifier = true;
}
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 = translateMouseButton(detail);
// 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);
if (m_triPressModifier) {
touchEvent.release(1, windowPos);
touchEvent.release(2, windowPos);
}
if (m_quadPressModifier) {
touchEvent.release(1, windowPos);
touchEvent.release(2, windowPos);
touchEvent.release(3, windowPos);
}
touchEvent.commit(false /* processEvents */);
m_leftButtonIsPressed = false;
m_triPressModifier = false;
m_quadPressModifier = false;
return true;
}
bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
{
if (!m_leftButtonIsPressed) {
return true;
}
Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
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);
if (m_triPressModifier) {
if (qtMod == TRI_PRESS_MODIFIER) {
touchEvent.move(1, windowPos);
touchEvent.move(2, windowPos);
} else {
// released modifiers
touchEvent.release(1, windowPos);
touchEvent.release(2, windowPos);
m_triPressModifier = false;
}
}
if (m_quadPressModifier) {
if (qtMod == QUAD_PRESS_MODIFIER) {
touchEvent.move(1, windowPos);
touchEvent.move(2, windowPos);
touchEvent.move(3, windowPos);
} else {
touchEvent.release(1, windowPos);
touchEvent.release(2, windowPos);
touchEvent.release(3, windowPos);
m_quadPressModifier = false;
}
}
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::enabled() const
{
return m_enabled;
}
void MouseTouchAdaptor::setEnabled(bool value)
{
if (value != m_enabled) {
m_enabled = value;
Q_EMIT enabledChanged(value);
}
}