Windows QPA/wintab: Register tablet devices

Inherit a class from QPointingDevice with a shared pointer
to the data for a physical cursor.

As a drive-by, keep the event time in QWindowsTabletSupport
for leave proximity events that do not have a time associated.
The previous code was relying on QWindowSystemInterface for
this, which may be at odds with the tablet's time.

Refactor and streamline the code a bit.

Task-number: QTBUG-46412
Change-Id: I2f4fab25a49a9d9f1befbd7fc040e8eb23be71ff
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
bb10
Friedemann Kleint 2020-10-15 13:30:24 +02:00 committed by Shawn Rutledge
parent e8873003f0
commit abb5f0d376
2 changed files with 230 additions and 116 deletions

View File

@ -133,11 +133,10 @@ QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t)
{
QDebugStateSaver saver(d);
d.nospace();
d << "TabletDevice id:" << t.uniqueId << " pressure: " << t.minPressure
d << "TabletDevice id:" << t.systemId << " pressure: " << t.minPressure
<< ".." << t.maxPressure << " tan pressure: " << t.minTanPressure << ".."
<< t.maxTanPressure << " area: (" << t.minX << ',' << t.minY << ',' << t.minZ
<< ")..(" << t.maxX << ',' << t.maxY << ',' << t.maxZ << ") device "
<< t.currentDevice << " pointer " << t.currentPointerType;
<< ")..(" << t.maxX << ',' << t.maxY << ',' << t.maxZ << ')';
return d;
}
@ -166,6 +165,48 @@ QDebug operator<<(QDebug d, const LOGCONTEXT &lc)
}
#endif // !QT_NO_DEBUG_STREAM
QWinTabPointingDevice *createInputDevice(const QSharedPointer<QWindowsTabletDeviceData> &d,
QInputDevice::DeviceType devType,
QPointingDevice::PointerType pointerType)
{
const qint64 uniqueId = d->systemId | (qint64(devType) << 32)
| (qint64(pointerType) << 48L);
QInputDevice::Capabilities caps(QInputDevice::Capability::Position
| QInputDevice::Capability::Pressure
| QInputDevice::Capability::MouseEmulation
| QInputDevice::Capability::Hover);
if (d->zCapability)
caps |= QInputDevice::Capability::ZPosition;
if (d->tiltCapability) {
caps |= QInputDevice::Capability::XTilt
| QInputDevice::Capability::YTilt
| QInputDevice::Capability::Rotation
| QInputDevice::Capability::TangentialPressure;
}
auto result = new QWinTabPointingDevice(d, QStringLiteral("wintab"), d->systemId,
devType, pointerType, caps, 1,
d->buttonsMap.size(), QString(),
QPointingDeviceUniqueId::fromNumericId(uniqueId));
QWindowSystemInterface::registerInputDevice(result);
return result;
}
QWinTabPointingDevice::QWinTabPointingDevice(const QWinTabPointingDevice::DeviceDataPtr &data,
const QString &name, qint64 systemId,
QInputDevice::DeviceType devType,
QPointingDevice::PointerType pType,
QInputDevice::Capabilities caps,
int maxPoints, int buttonCount,
const QString &seatName,
QPointingDeviceUniqueId uniqueId,
QObject *parent)
: QPointingDevice(name, systemId, devType, pType, caps, maxPoints, buttonCount,
seatName, uniqueId, parent),
m_deviceData(data)
{
}
QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL;
/*!
@ -320,14 +361,6 @@ void QWindowsTabletSupport::notifyActivate()
qCDebug(lcQpaTablet) << __FUNCTION__ << result;
}
static inline int indexOfDevice(const QList<QWindowsTabletDeviceData> &devices, qint64 uniqueId)
{
for (int i = 0; i < devices.size(); ++i)
if (devices.at(i).uniqueId == uniqueId)
return i;
return -1;
}
static inline QInputDevice::DeviceType deviceType(const UINT cursorType)
{
if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902))
@ -366,10 +399,65 @@ static inline QPointingDevice::PointerType pointerType(unsigned currentCursor)
return QPointingDevice::PointerType::Unknown;
}
QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT cursorType) const
inline void QWindowsTabletSupport::enterProximity(ulong time, QWindow *window)
{
enterLeaveProximity(true, time, window);
}
inline void QWindowsTabletSupport::leaveProximity(ulong time, QWindow *window)
{
enterLeaveProximity(false, time, window);
}
void QWindowsTabletSupport::enterLeaveProximity(bool enter, ulong time, QWindow *window)
{
Q_ASSERT(!m_currentDevice.isNull());
if (time == 0) // Some leave events do not have a time associated
++m_eventTime;
else
m_eventTime = time;
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, m_eventTime,
m_currentDevice.data(),
enter);
}
QWindowsTabletSupport::DevicePtr QWindowsTabletSupport::findDevice(qint64 systemId) const
{
for (const auto &d : m_devices) {
if (d->deviceData()->systemId == systemId)
return d;
}
return {};
}
QWindowsTabletSupport::DevicePtr QWindowsTabletSupport::findDevice(qint64 systemId,
QInputDevice::DeviceType deviceType,
QPointingDevice::PointerType pointerType) const
{
for (const auto &d : m_devices) {
if (d->deviceData()->systemId == systemId && d->type() == deviceType
&& d->pointerType() == pointerType) {
return d;
}
}
return {};
}
// Clone a device for a new pointer type.
QWindowsTabletSupport::DevicePtr QWindowsTabletSupport::clonePhysicalDevice(qint64 systemId,
QInputDevice::DeviceType deviceType,
QPointingDevice::PointerType pointerType)
{
auto similar = findDevice(systemId);
if (similar.isNull())
return {};
DevicePtr result(createInputDevice(similar->deviceData(), deviceType, pointerType));
m_devices.append(result);
return result;
}
void QWindowsTabletSupport::updateData(QWindowsTabletDeviceData *data) const
{
QWindowsTabletDeviceData result;
result.uniqueId = uniqueId;
/* browse WinTab's many info items to discover pressure handling. */
AXIS axis;
LOGCONTEXT lc;
@ -377,22 +465,40 @@ QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT
QWindowsTabletSupport::m_winTab32DLL.wTGet(m_context, &lc);
/* get the size of the pressure axis. */
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &axis);
result.minPressure = int(axis.axMin);
result.maxPressure = int(axis.axMax);
data->minPressure = int(axis.axMin);
data->maxPressure = int(axis.axMax);
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis);
result.minTanPressure = int(axis.axMin);
result.maxTanPressure = int(axis.axMax);
data->minTanPressure = int(axis.axMin);
data->maxTanPressure = int(axis.axMax);
LOGCONTEXT defaultLc;
/* get default region */
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFCONTEXT, 0, &defaultLc);
result.maxX = int(defaultLc.lcInExtX) - int(defaultLc.lcInOrgX);
result.maxY = int(defaultLc.lcInExtY) - int(defaultLc.lcInOrgY);
result.maxZ = int(defaultLc.lcInExtZ) - int(defaultLc.lcInOrgZ);
result.currentDevice = deviceType(cursorType);
result.zCapability = (cursorType == 0x0004);
return result;
data->maxX = int(defaultLc.lcInExtX) - int(defaultLc.lcInOrgX);
data->maxY = int(defaultLc.lcInExtY) - int(defaultLc.lcInOrgY);
data->maxZ = int(defaultLc.lcInExtZ) - int(defaultLc.lcInOrgZ);
}
void QWindowsTabletSupport::updateButtons(unsigned currentCursor, QWindowsTabletDeviceData *data) const
{
// We should check button map for changes on every proximity event, not
// only during initialization phase.
// WARNING: in 2016 there were some Wacom tablet drivers, which could mess up
// button mapping if the remapped button was pressed, while the
// application **didn't have input focus**. This bug is somehow
// related to the fact that Wacom drivers allow user to configure
// per-application button-mappings. If the bug shows up again,
// just move this button-map fetching into initialization block.
//
// See https://bugs.kde.org/show_bug.cgi?id=359561
BYTE logicalButtons[32];
memset(logicalButtons, 0, 32);
m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_SYSBTNMAP, &logicalButtons);
data->buttonsMap.clear();
data->buttonsMap[0x1] = logicalButtons[0];
data->buttonsMap[0x2] = logicalButtons[1];
data->buttonsMap[0x4] = logicalButtons[2];
}
bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam)
@ -401,21 +507,11 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer);
if (!LOWORD(lParam)) {
qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice;
if (m_currentDevice < 0 || m_currentDevice >= m_devices.size()) // QTBUG-65120, spurious leave observed
if (m_currentDevice.isNull()) // QTBUG-65120, spurious leave observed
return false;
qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice.data();
m_state = PenUp;
if (totalPacks > 0) {
QWindowSystemInterface::handleTabletLeaveProximityEvent(proximityBuffer[0].pkTime,
int(m_devices.at(m_currentDevice).currentDevice),
int(m_devices.at(m_currentDevice).currentPointerType),
m_devices.at(m_currentDevice).uniqueId);
} else {
QWindowSystemInterface::handleTabletLeaveProximityEvent(int(m_devices.at(m_currentDevice).currentDevice),
int(m_devices.at(m_currentDevice).currentPointerType),
m_devices.at(m_currentDevice).uniqueId);
}
leaveProximity(totalPacks > 0 ? proximityBuffer[0].pkTime : 0u);
return true;
}
@ -425,53 +521,37 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
const UINT currentCursor = proximityBuffer[0].pkCursor;
UINT physicalCursorId;
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId);
const qint64 systemId = physicalCursorId;
UINT cursorType;
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_TYPE, &cursorType);
const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId);
const QInputDevice::DeviceType currentType = deviceType(cursorType);
const QPointingDevice::PointerType currentPointerType = pointerType(currentCursor);
// initializing and updating the cursor should be done in response to
// WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send
// the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES
m_currentDevice = indexOfDevice(m_devices, uniqueId);
if (m_currentDevice < 0) {
m_currentDevice = m_devices.size();
m_devices.push_back(tabletInit(uniqueId, cursorType));
} else {
// The user can switch pressure sensitivity level in the driver,which
// will make our saved values invalid (this option is provided by Wacom
// drivers for compatibility reasons, and it can be adjusted on the fly)
m_devices[m_currentDevice] = tabletInit(uniqueId, cursorType);
m_currentDevice = findDevice(systemId, currentType, currentPointerType);
if (m_currentDevice.isNull())
m_currentDevice = clonePhysicalDevice(systemId, currentType, currentPointerType);
if (m_currentDevice.isNull()) {
QWinTabPointingDevice::DeviceDataPtr data(new QWindowsTabletDeviceData);
data->systemId = systemId;
data->tiltCapability = m_tiltSupport;
data->zCapability = (cursorType == 0x0004);
updateButtons(currentCursor, data.data());
m_currentDevice.reset(createInputDevice(data, currentType, currentPointerType));
m_devices.append(m_currentDevice);
}
/**
* We should check button map for changes on every proximity event, not
* only during initialization phase.
*
* WARNING: in 2016 there were some Wacom table drivers, which could mess up
* button mapping if the remapped button was pressed, while the
* application **didn't have input focus**. This bug is somehow
* related to the fact that Wacom drivers allow user to configure
* per-application button-mappings. If the bug shows up again,
* just move this button-map fetching into initialization block.
*
* See https://bugs.kde.org/show_bug.cgi?id=359561
*/
BYTE logicalButtons[32];
memset(logicalButtons, 0, 32);
m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_SYSBTNMAP, &logicalButtons);
m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0];
m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1];
m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2];
// The user can switch pressure sensitivity level in the driver,which
// will make our saved values invalid (this option is provided by Wacom
// drivers for compatibility reasons, and it can be adjusted on the fly)
updateData(m_currentDevice->deviceData().data());
m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor);
m_state = PenProximity;
qCDebug(lcQpaTablet) << "enter proximity for device #"
<< m_currentDevice << m_devices.at(m_currentDevice);
// TODO use the version taking a QPointingDevice, and own those instances; replace QWindowsTabletDeviceData
// TODO QWindowSystemInterface::registerInputDevice() as early as possible, and before sending any events from it
QWindowSystemInterface::handleTabletEnterProximityEvent(proximityBuffer[0].pkTime,
int(m_devices.at(m_currentDevice).currentDevice),
int(m_devices.at(m_currentDevice).currentPointerType),
m_devices.at(m_currentDevice).uniqueId);
<< m_currentDevice.data();
enterProximity(proximityBuffer[0].pkTime);
return true;
}
@ -525,11 +605,10 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
{
static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue.
const int packetCount = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, TabletPacketQSize, &localPacketBuf);
if (!packetCount || m_currentDevice < 0)
if (!packetCount || m_currentDevice.isNull())
return false;
const auto currentDevice = m_devices.at(m_currentDevice).currentDevice;
const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId;
const QWindowsTabletDeviceData &current = *m_currentDevice->deviceData();
// The tablet can be used in 2 different modes (reflected in enum Mode),
// depending on its settings:
@ -549,8 +628,7 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
if (QWindowsContext::verbose > 1) {
qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount
<< "mode=" << m_mode << "target:"
<< QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target;
<< "mode=" << m_mode;
}
const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
@ -558,33 +636,31 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
for (int i = 0; i < packetCount ; ++i) {
const PACKET &packet = localPacketBuf[i];
const int z = m_devices.at(m_currentDevice).zCapability ? int(packet.pkZ) : 0;
const int z = current.zCapability ? int(packet.pkZ) : 0;
const auto currentPointer = m_devices.at(m_currentDevice).currentPointerType;
const auto packetPointerType = pointerType(packet.pkCursor);
const Qt::MouseButtons buttons =
convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice));
convertTabletButtons(packet.pkButtons, current);
if (buttons == Qt::NoButton && packetPointerType != currentPointer) {
QWindowSystemInterface::handleTabletLeaveProximityEvent(packet.pkTime,
int(currentDevice),
int(currentPointer),
uniqueId);
m_devices[m_currentDevice].currentPointerType = packetPointerType;
QWindowSystemInterface::handleTabletEnterProximityEvent(packet.pkTime,
int(currentDevice),
int(packetPointerType),
uniqueId);
if (buttons == Qt::NoButton && packetPointerType != m_currentDevice->pointerType()) {
leaveProximity(packet.pkTime);
Q_ASSERT(!m_currentDevice.isNull());
// Pointer type changed, find or clone a new device for this physical cursor.
const qint64 systemId = m_currentDevice->systemId();
const QInputDevice::DeviceType type = m_currentDevice->type();
m_currentDevice = findDevice(systemId, type, packetPointerType);
if (m_currentDevice.isNull())
m_currentDevice = clonePhysicalDevice(systemId, type, packetPointerType);
Q_ASSERT(!m_currentDevice.isNull());
enterProximity(packet.pkTime);
}
QPointF globalPosF =
m_devices.at(m_currentDevice).scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea);
current.scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea);
QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
// Pass to window that grabbed it.
QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(m_currentDevice->uniqueId().numericId()).target;
// Get Mouse Position and compare to tablet info
const QPoint mouseLocation = QWindowsCursor::mousePosition();
@ -608,12 +684,12 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
Q_ASSERT(platformWindow);
const QPoint localPos = platformWindow->mapFromGlobal(globalPos);
const qreal pressureNew = packet.pkButtons && (currentPointer == QPointingDevice::PointerType::Pen || currentPointer == QPointingDevice::PointerType::Eraser) ?
m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) :
qreal(0);
const qreal tangentialPressure = currentDevice == QInputDevice::DeviceType::Airbrush ?
m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) :
qreal(0);
const qreal pressureNew = packet.pkButtons
&& (m_currentDevice->pointerType() == QPointingDevice::PointerType::Pen
|| m_currentDevice->pointerType() == QPointingDevice::PointerType::Eraser)
? current.scalePressure(packet.pkNormalPressure) : qreal(0);
const qreal tangentialPressure = m_currentDevice->type() == QInputDevice::DeviceType::Airbrush
? current.scaleTangentialPressure(packet.pkTangentPressure) : qreal(0);
int tiltX = 0;
int tiltY = 0;
@ -642,17 +718,18 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
qCDebug(lcQpaTablet)
<< "Packet #" << i << '/' << packetCount << "button:" << packet.pkButtons
<< globalPosF << z << "to:" << target << localPos << "(packet" << packet.pkX
<< packet.pkY << ") dev:" << currentDevice << "pointer:"
<< currentPointer << "P:" << pressureNew << "tilt:" << tiltX << ','
<< tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation;
<< packet.pkY << ") dev:" << m_currentDevice->type() << "pointer:"
<< m_currentDevice->pointerType() << "P:" << pressureNew << "tilt:" << tiltX << ','
<< tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation
<< " target=" << target;
}
QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
int(currentDevice), int(currentPointer),
buttons,
pressureNew, tiltX, tiltY,
m_eventTime = packet.pkTime;
QWindowSystemInterface::handleTabletEvent(target, packet.pkTime,
m_currentDevice.data(),
QPointF(localPos), globalPosF,
buttons, pressureNew, tiltX, tiltY,
tangentialPressure, rotation, z,
uniqueId,
keyboardModifiers);
}
return true;

View File

@ -47,6 +47,7 @@
#include <QtCore/qhash.h>
#include <QtCore/qlist.h>
#include <QtCore/qpoint.h>
#include <QtCore/qsharedpointer.h>
#include <wintab.h>
@ -83,6 +84,8 @@ struct QWindowsWinTab32DLL
PtrWTQueueSizeSet wTQueueSizeSet = nullptr;
};
// Data associated with a physical cursor (system ID) which is shared between
// devices of varying device type/pointer type.
struct QWindowsTabletDeviceData
{
QPointF scaleCoordinates(int coordX, int coordY,const QRect &targetArea) const;
@ -99,13 +102,31 @@ struct QWindowsTabletDeviceData
int maxY = 0;
int minZ = 0;
int maxZ = 0;
qint64 uniqueId = 0;
QInputDevice::DeviceType currentDevice = QInputDevice::DeviceType::Unknown;
QPointingDevice::PointerType currentPointerType = QPointingDevice::PointerType::Unknown;
qint64 systemId = 0;
bool zCapability = false;
bool tiltCapability = false;
QHash<quint8, quint8> buttonsMap;
};
class QWinTabPointingDevice : public QPointingDevice
{
public:
using DeviceDataPtr = QSharedPointer<QWindowsTabletDeviceData>;
explicit QWinTabPointingDevice(const DeviceDataPtr &data,
const QString &name, qint64 systemId,
QInputDevice::DeviceType devType,
PointerType pType, Capabilities caps, int maxPoints,
int buttonCount, const QString &seatName = QString(),
QPointingDeviceUniqueId uniqueId = QPointingDeviceUniqueId(),
QObject *parent = nullptr);
const DeviceDataPtr &deviceData() const { return m_deviceData; }
private:
DeviceDataPtr m_deviceData;
};
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t);
#endif
@ -117,6 +138,9 @@ class QWindowsTabletSupport
explicit QWindowsTabletSupport(HWND window, HCTX context);
public:
using DevicePtr = QSharedPointer<QWinTabPointingDevice>;
using Devices = QList<DevicePtr>;
enum Mode
{
PenMode,
@ -146,16 +170,29 @@ public:
private:
unsigned options() const;
QWindowsTabletDeviceData tabletInit(qint64 uniqueId, UINT cursorType) const;
void updateData(QWindowsTabletDeviceData *data) const;
void updateButtons(unsigned currentCursor, QWindowsTabletDeviceData *data) const;
void enterProximity(ulong time = 0, QWindow *window = nullptr);
void leaveProximity(ulong time = 0, QWindow *window = nullptr);
void enterLeaveProximity(bool enter, ulong time, QWindow *window = nullptr);
DevicePtr findDevice(qint64 systemId) const;
DevicePtr findDevice(qint64 systemId,
QInputDevice::DeviceType deviceType,
QPointingDevice::PointerType pointerType) const;
DevicePtr clonePhysicalDevice(qint64 systemId,
QInputDevice::DeviceType deviceType,
QPointingDevice::PointerType pointerType);
static QWindowsWinTab32DLL m_winTab32DLL;
const HWND m_window;
const HCTX m_context;
static int m_absoluteRange;
bool m_tiltSupport = false;
QList<QWindowsTabletDeviceData> m_devices;
int m_currentDevice = -1;
Devices m_devices;
DevicePtr m_currentDevice;
Mode m_mode = PenMode;
State m_state = PenUp;
ulong m_eventTime = 0;
};
QT_END_NAMESPACE