summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/windows/qwindowstabletsupport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/windows/qwindowstabletsupport.cpp')
-rw-r--r--src/plugins/platforms/windows/qwindowstabletsupport.cpp473
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