diff options
Diffstat (limited to 'src/plugins/platforms/windows/qwindowstabletsupport.cpp')
-rw-r--r-- | src/plugins/platforms/windows/qwindowstabletsupport.cpp | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp new file mode 100644 index 0000000000..4a5d7b5a78 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -0,0 +1,473 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowstabletsupport.h" + +#ifndef QT_NO_TABLETEVENT + +#include "qwindowscontext.h" +#include "qwindowskeymapper.h" +#include "qwindowswindow.h" + +#include <qpa/qwindowsysteminterface.h> + +#include <QtGui/QTabletEvent> +#include <QtGui/QScreen> +#include <QtGui/QGuiApplication> +#include <QtGui/QWindow> +#include <QtCore/QDebug> +#include <QtCore/QScopedArrayPointer> +#include <QtCore/QtMath> + +#include <private/qguiapplication_p.h> +#include <QtCore/private/qsystemlibrary_p.h> + +// Note: The definition of the PACKET structure in pktdef.h depends on this define. +#define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_Z) +#include <pktdef.h> + +QT_BEGIN_NAMESPACE + +enum { + PacketMode = 0, + TabletPacketQSize = 128, + DeviceIdMask = 0xFF6, // device type mask && device color mask + CursorTypeBitMask = 0x0F06 // bitmask to find the specific cursor type (see Wacom FAQ) +}; + +extern "C" LRESULT QT_WIN_CALLBACK qWindowsTabletSupportWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WT_PROXIMITY: + if (QWindowsContext::instance()->tabletSupport()->translateTabletProximityEvent(wParam, lParam)) + return 0; + break; + case WT_PACKET: + if (QWindowsContext::instance()->tabletSupport()->translateTabletPacketEvent()) + return 0; + break; + } + return DefWindowProc(hwnd, message, wParam, lParam); +} + + +// Scale tablet coordinates to screen coordinates. + +static inline int sign(int x) +{ + return x >= 0 ? 1 : -1; +} + +inline QPointF QWindowsTabletDeviceData::scaleCoordinates(int coordX, int coordY, const QRect &targetArea) const +{ + const int targetX = targetArea.x(); + const int targetY = targetArea.y(); + const int targetWidth = targetArea.width(); + const int targetHeight = targetArea.height(); + + const qreal x = sign(targetWidth) == sign(maxX) ? + ((coordX - minX) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX : + ((qAbs(maxX) - (coordX - minX)) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX; + + const qreal y = sign(targetHeight) == sign(maxY) ? + ((coordY - minY) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY : + ((qAbs(maxY) - (coordY - minY)) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY; + + return QPointF(x, y); +} + +QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL; + +/*! + \class QWindowsWinTab32DLL QWindowsTabletSupport + \brief Functions from wintabl32.dll shipped with WACOM tablets used by QWindowsTabletSupport. + + \internal + \ingroup qt-lighthouse-win +*/ + +bool QWindowsWinTab32DLL::init() +{ + if (wTInfo) + return true; + QSystemLibrary library(QStringLiteral("wintab32")); + if (!library.load()) + return false; + wTOpen = (PtrWTOpen)library.resolve("WTOpenW"); + wTClose = (PtrWTClose)library.resolve("WTClose"); + wTInfo = (PtrWTInfo)library.resolve("WTInfoW"); + wTEnable = (PtrWTEnable)library.resolve("WTEnable"); + wTOverlap = (PtrWTEnable)library.resolve("WTOverlap"); + wTPacketsGet = (PtrWTPacketsGet)library.resolve("WTPacketsGet"); + wTGet = (PtrWTGet)library.resolve("WTGetW"); + wTQueueSizeGet = (PtrWTQueueSizeGet)library.resolve("WTQueueSizeGet"); + wTQueueSizeSet = (PtrWTQueueSizeSet)library.resolve("WTQueueSizeSet"); + return wTOpen && wTClose && wTInfo && wTEnable && wTOverlap && wTPacketsGet && wTQueueSizeGet && wTQueueSizeSet; +} + +/*! + \class QWindowsTabletSupport + \brief Tablet support for Windows. + + Support for WACOM tablets. + + \sa http://www.wacomeng.com/windows/docs/Wintab_v140.htm + + \internal + \since 5.2 + \ingroup qt-lighthouse-win +*/ + +QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context) + : m_window(window) + , m_context(context) + , m_tiltSupport(false) + , m_currentDevice(-1) +{ + AXIS orientation[3]; + // Some tablets don't support tilt, check if it is possible, + if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation)) + m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution; +} + +QWindowsTabletSupport::~QWindowsTabletSupport() +{ + QWindowsTabletSupport::m_winTab32DLL.wTClose(m_context); + DestroyWindow(m_window); +} + +QWindowsTabletSupport *QWindowsTabletSupport::create() +{ + if (!m_winTab32DLL.init()) + return 0; + const HWND window = QWindowsContext::instance()->createDummyWindow(QStringLiteral("TabletDummyWindow"), + L"TabletDummyWindow", + qWindowsTabletSupportWndProc); + if (!window) { + if (QWindowsContext::verboseTablet) + qWarning() << __FUNCTION__ << "Unable to create window for tablet."; + return 0; + } + LOGCONTEXT lcMine; + // build our context from the default context + QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine); + // Go for the raw coordinates, the tablet event will return good stuff + lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES; + lcMine.lcPktData = lcMine.lcMoveMask = PACKETDATA; + lcMine.lcPktMode = PacketMode; + lcMine.lcOutOrgX = 0; + lcMine.lcOutExtX = lcMine.lcInExtX; + lcMine.lcOutOrgY = 0; + lcMine.lcOutExtY = -lcMine.lcInExtY; + const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true); + if (!context) { + if (QWindowsContext::verboseTablet) + qWarning() << __FUNCTION__ << "Unable to open tablet."; + DestroyWindow(window); + return 0; + + } + // Set the size of the Packet Queue to the correct size + const int currentQueueSize = QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeGet(context); + if (currentQueueSize != TabletPacketQSize) { + if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, TabletPacketQSize)) { + if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, currentQueueSize)) { + qWarning() << "Unable to set queue size on tablet. The tablet will not work."; + QWindowsTabletSupport::m_winTab32DLL.wTClose(context); + DestroyWindow(window); + return 0; + } // cannot restore old size + } // cannot set + } // mismatch + if (QWindowsContext::verboseTablet) + qDebug("Opened tablet context %p on window %p, changed packet queue size %d -> %d", + context, window, currentQueueSize, TabletPacketQSize); + return new QWindowsTabletSupport(window, context); +} + +unsigned QWindowsTabletSupport::options() const +{ + UINT result = 0; + m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_CTXOPTIONS, &result); + return result; +} + +QString QWindowsTabletSupport::description() const +{ + const unsigned size = m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, 0); + if (!size) + return QString(); + QScopedPointer<TCHAR> winTabId(new TCHAR[size + 1]); + m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, winTabId.data()); + WORD implementationVersion = 0; + m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_IMPLVERSION, &implementationVersion); + WORD specificationVersion = 0; + m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_SPECVERSION, &specificationVersion); + const unsigned opts = options(); + QString result = QString::fromLatin1("%1 specification: v%2.%3 implementation: v%4.%5 options: 0x%6") + .arg(QString::fromWCharArray(winTabId.data())) + .arg(specificationVersion >> 8).arg(specificationVersion & 0xFF) + .arg(implementationVersion >> 8).arg(implementationVersion & 0xFF) + .arg(opts, 0, 16); + if (opts & CXO_MESSAGES) + result += QStringLiteral(" CXO_MESSAGES"); + if (opts & CXO_CSRMESSAGES) + result += QStringLiteral(" CXO_CSRMESSAGES"); + if (m_tiltSupport) + result += QStringLiteral(" tilt"); + return result; +} + +void QWindowsTabletSupport::notifyActivate() +{ + // Cooperate with other tablet applications, but when we get focus, I want to use the tablet. + const bool result = QWindowsTabletSupport::m_winTab32DLL.wTEnable(m_context, true) + && QWindowsTabletSupport::m_winTab32DLL.wTOverlap(m_context, true); + if (QWindowsContext::verboseTablet) + qDebug() << __FUNCTION__ << result; +} + +static inline int indexOfDevice(const QVector<QWindowsTabletDeviceData> &devices, qint64 uniqueId) +{ + for (int i = 0; i < devices.size(); ++i) + if (devices.at(i).uniqueId == uniqueId) + return i; + return -1; +} + +static inline QTabletEvent::TabletDevice deviceType(const UINT cursorType) +{ + if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902)) + return QTabletEvent::Stylus; + switch (cursorType & CursorTypeBitMask) { + case 0x0802: + return QTabletEvent::Stylus; + case 0x0902: + return QTabletEvent::Airbrush; + case 0x0004: + return QTabletEvent::FourDMouse; + case 0x0006: + return QTabletEvent::Puck; + case 0x0804: + return QTabletEvent::RotationStylus; + default: + break; + } + return QTabletEvent::NoDevice; +} + +static inline QTabletEvent::PointerType pointerType(unsigned currentCursor) +{ + switch (currentCursor % 3) { // %3 for dual track + case 0: + return QTabletEvent::Cursor; + case 1: + return QTabletEvent::Pen; + case 2: + return QTabletEvent::Eraser; + default: + break; + } + return QTabletEvent::UnknownPointer; +} + +QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t) +{ + d << "TabletDevice id:" << t.uniqueId << " 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; + return d; +} + +QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(const quint64 uniqueId, const UINT cursorType) const +{ + QWindowsTabletDeviceData result; + result.uniqueId = uniqueId; + /* browse WinTab's many info items to discover pressure handling. */ + AXIS axis; + LOGCONTEXT lc; + /* get the current context for its device variable. */ + 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); + + QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis); + result.minTanPressure = int(axis.axMin); + result.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); + return result; +} + +bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam) +{ + const bool enteredProximity = LOWORD(lParam) != 0; + PACKET proximityBuffer[1]; // we are only interested in the first packet in this case + const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer); + if (!totalPacks) + return false; + const UINT currentCursor = proximityBuffer[0].pkCursor; + UINT physicalCursorId; + QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId); + UINT cursorType; + QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_TYPE, &cursorType); + const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId); + // 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)); + } + m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor); + if (QWindowsContext::verboseTablet) + qDebug() << __FUNCTION__ << (enteredProximity ? "enter" : "leave") + << " proximity for device #" + << m_currentDevice << m_devices.at(m_currentDevice); + if (enteredProximity) { + QWindowSystemInterface::handleTabletEnterProximityEvent(m_devices.at(m_currentDevice).currentDevice, + m_devices.at(m_currentDevice).currentPointerType, + m_devices.at(m_currentDevice).uniqueId); + } else { + QWindowSystemInterface::handleTabletLeaveProximityEvent(m_devices.at(m_currentDevice).currentDevice, + m_devices.at(m_currentDevice).currentPointerType, + m_devices.at(m_currentDevice).uniqueId); + } + return true; +} + +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) + return false; + + const int currentDevice = m_devices.at(m_currentDevice).currentDevice; + const int currentPointer = m_devices.at(m_currentDevice).currentPointerType; + + // When entering proximity, the tablet driver snaps the mouse pointer to the + // tablet position scaled to the virtual desktop and keeps it in sync. + const QRect virtualDesktopArea = QGuiApplication::primaryScreen()->virtualGeometry(); + + if (QWindowsContext::verboseTablet) + qDebug() << __FUNCTION__ << "processing " << packetCount + << "target:" << QGuiApplicationPrivate::tabletPressTarget; + + const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + + for (int i = 0; i < packetCount ; ++i) { + const PACKET &packet = localPacketBuf[i]; + + const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0; + const QPointF globalPosF = m_devices.at(m_currentDevice).scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea); + + QWindow *target = QGuiApplicationPrivate::tabletPressTarget; // Pass to window that grabbed it. + const QPoint globalPos = globalPosF.toPoint(); + if (!target) + if (QPlatformWindow *pw = QWindowsContext::instance()->findPlatformWindowAt(GetDesktopWindow(), globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT)) + target = pw->window(); + if (!target) + continue; + + const QPoint localPos = target->mapFromGlobal(globalPos); + + const qreal pressureNew = packet.pkButtons && (currentPointer == QTabletEvent::Pen || currentPointer == QTabletEvent::Eraser) ? + m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) : + qreal(0); + const qreal tangentialPressure = currentDevice == QTabletEvent::Airbrush ? + m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) : + qreal(0); + + int tiltX = 0; + int tiltY = 0; + qreal rotation = 0; + if (m_tiltSupport) { + // Convert from azimuth and altitude to x tilt and y tilt. What + // follows is the optimized version. Here are the equations used: + // X = sin(azimuth) * cos(altitude) + // Y = cos(azimuth) * cos(altitude) + // Z = sin(altitude) + // X Tilt = arctan(X / Z) + // Y Tilt = arctan(Y / Z) + const double radAzim = (packet.pkOrientation.orAzimuth / 10) * (M_PI / 180); + const double tanAlt = tan((abs(packet.pkOrientation.orAltitude / 10)) * (M_PI / 180)); + + const double degX = atan(sin(radAzim) / tanAlt); + const double degY = atan(cos(radAzim) / tanAlt); + tiltX = int(degX * (180 / M_PI)); + tiltY = int(-degY * (180 / M_PI)); + rotation = packet.pkOrientation.orTwist; + } + + if (QWindowsContext::verboseTablet > 1) { + qDebug() + << "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; + } + + QWindowSystemInterface::handleTabletEvent(target, packet.pkButtons, localPos, globalPosF, + currentDevice, currentPointer, + pressureNew, tiltX, tiltY, + tangentialPressure, rotation, z, + m_devices.at(m_currentDevice).uniqueId, + keyboardModifiers); + } + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_TABLETEVENT |