567 lines
24 KiB
C++
567 lines
24 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** 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 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 Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or 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.GPL2 and 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-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qwindowstabletsupport.h"
|
|
|
|
#include "qwindowscontext.h"
|
|
#include "qwindowskeymapper.h"
|
|
#include "qwindowswindow.h"
|
|
#include "qwindowsscreen.h"
|
|
|
|
#include <qpa/qwindowsysteminterface.h>
|
|
|
|
#include <QtGui/qevent.h>
|
|
#include <QtGui/qscreen.h>
|
|
#include <QtGui/qguiapplication.h>
|
|
#include <QtGui/qwindow.h>
|
|
#include <QtCore/qdebug.h>
|
|
#include <QtCore/qvarlengtharray.h>
|
|
#include <QtCore/qmath.h>
|
|
|
|
#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 | PK_TIME)
|
|
#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 {x, y};
|
|
}
|
|
|
|
template <class Stream>
|
|
static void formatOptions(Stream &str, unsigned options)
|
|
{
|
|
if (options & CXO_SYSTEM)
|
|
str << " CXO_SYSTEM";
|
|
if (options & CXO_PEN)
|
|
str << " CXO_PEN";
|
|
if (options & CXO_MESSAGES)
|
|
str << " CXO_MESSAGES";
|
|
if (options & CXO_MARGIN)
|
|
str << " CXO_MARGIN";
|
|
if (options & CXO_MGNINSIDE)
|
|
str << " CXO_MGNINSIDE";
|
|
if (options & CXO_CSRMESSAGES)
|
|
str << " CXO_CSRMESSAGES";
|
|
}
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t)
|
|
{
|
|
QDebugStateSaver saver(d);
|
|
d.nospace();
|
|
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;
|
|
}
|
|
|
|
QDebug operator<<(QDebug d, const LOGCONTEXT &lc)
|
|
{
|
|
QDebugStateSaver saver(d);
|
|
d.nospace();
|
|
d << "LOGCONTEXT(\"" << QString::fromWCharArray(lc.lcName) << "\", options=0x"
|
|
<< hex << lc.lcOptions << dec;
|
|
formatOptions(d, lc.lcOptions);
|
|
d << ", status=0x" << hex << lc.lcStatus << ", device=0x" << lc.lcDevice
|
|
<< dec << ", PktRate=" << lc.lcPktRate
|
|
<< ", PktData=" << lc.lcPktData << ", PktMode=" << lc.lcPktMode
|
|
<< ", MoveMask=0x" << hex << lc.lcMoveMask << ", BtnDnMask=0x" << lc.lcBtnDnMask
|
|
<< ", BtnUpMask=0x" << lc.lcBtnUpMask << dec << ", SysMode=" << lc.lcSysMode
|
|
<< ", InOrg=(" << lc.lcInOrgX << ", " << lc.lcInOrgY << ", " << lc.lcInOrgZ
|
|
<< "), InExt=(" << lc.lcInExtX << ", " << lc.lcInExtY << ", " << lc.lcInExtZ
|
|
<< ") OutOrg=(" << lc.lcOutOrgX << ", " << lc.lcOutOrgY << ", "
|
|
<< lc.lcOutOrgZ << "), OutExt=(" << lc.lcOutExtX << ", " << lc.lcOutExtY
|
|
<< ", " << lc.lcOutExtZ
|
|
<< "), Sens=(" << lc.lcSensX << ", " << lc.lcSensX << ", " << lc.lcSensZ
|
|
<< ") SysOrg=(" << lc.lcSysOrgX << ", " << lc.lcSysOrgY
|
|
<< "), SysExt=(" << lc.lcSysExtX << ", " << lc.lcSysExtY
|
|
<< "), SysSens=(" << lc.lcSysSensX << ", " << lc.lcSysSensY << "))";
|
|
return d;
|
|
}
|
|
#endif // !QT_NO_DEBUG_STREAM
|
|
|
|
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)
|
|
{
|
|
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 nullptr;
|
|
const HWND window = QWindowsContext::instance()->createDummyWindow(QStringLiteral("TabletDummyWindow"),
|
|
L"TabletDummyWindow",
|
|
qWindowsTabletSupportWndProc);
|
|
if (!window) {
|
|
qCWarning(lcQpaTablet) << __FUNCTION__ << "Unable to create window for tablet.";
|
|
return nullptr;
|
|
}
|
|
LOGCONTEXT lcMine;
|
|
// build our context from the default context
|
|
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine);
|
|
qCDebug(lcQpaTablet) << "Default: " << 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;
|
|
qCDebug(lcQpaTablet) << "Requesting: " << lcMine;
|
|
const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true);
|
|
if (!context) {
|
|
qCDebug(lcQpaTablet) << __FUNCTION__ << "Unable to open tablet.";
|
|
DestroyWindow(window);
|
|
return nullptr;
|
|
|
|
}
|
|
// 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 nullptr;
|
|
} // cannot restore old size
|
|
} // cannot set
|
|
} // mismatch
|
|
qCDebug(lcQpaTablet) << "Opened tablet context " << context << " on window "
|
|
<< window << "changed packet queue size " << currentQueueSize
|
|
<< "->" << TabletPacketQSize << "\nobtained: " << lcMine;
|
|
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, nullptr);
|
|
if (!size)
|
|
return QString();
|
|
QVarLengthArray<TCHAR> winTabId(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();
|
|
WORD devices = 0;
|
|
m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NDEVICES, &devices);
|
|
WORD cursors = 0;
|
|
m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NCURSORS, &cursors);
|
|
WORD extensions = 0;
|
|
m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NEXTENSIONS, &extensions);
|
|
QString result;
|
|
QTextStream str(&result);
|
|
str << '"' << QString::fromWCharArray(winTabId.data())
|
|
<< "\" specification: v" << (specificationVersion >> 8)
|
|
<< '.' << (specificationVersion & 0xFF) << " implementation: v"
|
|
<< (implementationVersion >> 8) << '.' << (implementationVersion & 0xFF)
|
|
<< ' ' << devices << " device(s), " << cursors << " cursor(s), "
|
|
<< extensions << " extensions" << ", options: 0x" << hex << opts << dec;
|
|
formatOptions(str, opts);
|
|
if (m_tiltSupport)
|
|
str << " 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);
|
|
qCDebug(lcQpaTablet) << __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;
|
|
if (cursorType == 0x4020) // Surface Pro 2 tablet device
|
|
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;
|
|
}
|
|
|
|
QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, 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)
|
|
{
|
|
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 (!LOWORD(lParam)) {
|
|
qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice;
|
|
if (m_currentDevice < 0 || m_currentDevice >= m_devices.size()) // QTBUG-65120, spurious leave observed
|
|
return false;
|
|
m_state = PenUp;
|
|
if (totalPacks > 0) {
|
|
QWindowSystemInterface::handleTabletLeaveProximityEvent(proximityBuffer[0].pkTime,
|
|
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;
|
|
}
|
|
|
|
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);
|
|
m_state = PenProximity;
|
|
qCDebug(lcQpaTablet) << "enter proximity for device #"
|
|
<< m_currentDevice << m_devices.at(m_currentDevice);
|
|
QWindowSystemInterface::handleTabletEnterProximityEvent(proximityBuffer[0].pkTime,
|
|
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;
|
|
const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId;
|
|
|
|
// The tablet can be used in 2 different modes (reflected in enum Mode),
|
|
// depending on its settings:
|
|
// 1) Absolute (pen) mode:
|
|
// The coordinates are scaled to the virtual desktop (by default). The user
|
|
// can also choose to scale to the monitor or a region of the screen.
|
|
// When entering proximity, the tablet driver snaps the mouse pointer to the
|
|
// tablet position scaled to that area and keeps it in sync.
|
|
// 2) Relative (mouse) mode:
|
|
// The pen follows the mouse. The constant 'absoluteRange' specifies the
|
|
// manhattanLength difference for detecting if a tablet input device is in this mode,
|
|
// in which case we snap the position to the mouse position.
|
|
// It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext
|
|
// area is always the virtual desktop.
|
|
const QRect virtualDesktopArea =
|
|
QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle());
|
|
|
|
if (QWindowsContext::verbose > 1) {
|
|
qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount
|
|
<< "mode=" << m_mode << "target:"
|
|
<< QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target;
|
|
}
|
|
|
|
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;
|
|
|
|
QPointF globalPosF =
|
|
m_devices.at(m_currentDevice).scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea);
|
|
|
|
QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
|
|
|
|
// Get Mouse Position and compare to tablet info
|
|
const QPoint mouseLocation = QWindowsCursor::mousePosition();
|
|
if (m_state == PenProximity) {
|
|
m_state = PenDown;
|
|
m_mode = (mouseLocation - globalPosF).manhattanLength() > m_absoluteRange
|
|
? MouseMode : PenMode;
|
|
qCDebug(lcQpaTablet) << __FUNCTION__ << "mode=" << m_mode << "pen:"
|
|
<< globalPosF << "mouse:" << mouseLocation;
|
|
}
|
|
if (m_mode == MouseMode)
|
|
globalPosF = mouseLocation;
|
|
const QPoint globalPos = globalPosF.toPoint();
|
|
|
|
if (!target)
|
|
target = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT);
|
|
if (!target)
|
|
continue;
|
|
|
|
const QPlatformWindow *platformWindow = target->handle();
|
|
Q_ASSERT(platformWindow);
|
|
const QPoint localPos = platformWindow->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 = qDegreesToRadians(packet.pkOrientation.orAzimuth / 10.0);
|
|
const double tanAlt = std::tan(qDegreesToRadians(std::abs(packet.pkOrientation.orAltitude / 10.0)));
|
|
|
|
const double radX = std::atan(std::sin(radAzim) / tanAlt);
|
|
const double radY = std::atan(std::cos(radAzim) / tanAlt);
|
|
tiltX = int(qRadiansToDegrees(radX));
|
|
tiltY = int(qRadiansToDegrees(-radY));
|
|
rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0);
|
|
if (rotation > 180.0)
|
|
rotation -= 360.0;
|
|
}
|
|
|
|
if (QWindowsContext::verbose > 1) {
|
|
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;
|
|
}
|
|
|
|
QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
|
|
currentDevice, currentPointer,
|
|
static_cast<Qt::MouseButtons>(packet.pkButtons),
|
|
pressureNew, tiltX, tiltY,
|
|
tangentialPressure, rotation, z,
|
|
uniqueId,
|
|
keyboardModifiers);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|