1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qwindowstabletsupport.h"
41 
42 #include "qwindowscontext.h"
43 #include "qwindowskeymapper.h"
44 #include "qwindowswindow.h"
45 #include "qwindowsscreen.h"
46 
47 #include <qpa/qwindowsysteminterface.h>
48 
49 #include <QtGui/qevent.h>
50 #include <QtGui/qscreen.h>
51 #include <QtGui/qguiapplication.h>
52 #include <QtGui/qwindow.h>
53 #include <QtCore/qdebug.h>
54 #include <QtCore/qvarlengtharray.h>
55 #include <QtCore/qmath.h>
56 
57 #include <private/qguiapplication_p.h>
58 #include <QtCore/private/qsystemlibrary_p.h>
59 
60 // Note: The definition of the PACKET structure in pktdef.h depends on this define.
61 #define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_Z | PK_TIME)
62 #include <pktdef.h>
63 
64 QT_BEGIN_NAMESPACE
65 
66 enum {
67     PacketMode = 0,
68     TabletPacketQSize = 128,
69     DeviceIdMask = 0xFF6, // device type mask && device color mask
70     CursorTypeBitMask = 0x0F06 // bitmask to find the specific cursor type (see Wacom FAQ)
71 };
72 
qWindowsTabletSupportWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)73 extern "C" LRESULT QT_WIN_CALLBACK qWindowsTabletSupportWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
74 {
75     switch (message) {
76     case WT_PROXIMITY:
77         if (QWindowsContext::instance()->tabletSupport()->translateTabletProximityEvent(wParam, lParam))
78             return 0;
79         break;
80     case WT_PACKET:
81         if (QWindowsContext::instance()->tabletSupport()->translateTabletPacketEvent())
82             return 0;
83         break;
84     }
85     return DefWindowProc(hwnd, message, wParam, lParam);
86 }
87 
88 
89 // Scale tablet coordinates to screen coordinates.
90 
sign(int x)91 static inline int sign(int x)
92 {
93     return x >= 0 ? 1 : -1;
94 }
95 
scaleCoordinates(int coordX,int coordY,const QRect & targetArea) const96 inline QPointF QWindowsTabletDeviceData::scaleCoordinates(int coordX, int coordY, const QRect &targetArea) const
97 {
98     const int targetX = targetArea.x();
99     const int targetY = targetArea.y();
100     const int targetWidth = targetArea.width();
101     const int targetHeight = targetArea.height();
102 
103     const qreal x = sign(targetWidth) == sign(maxX) ?
104         ((coordX - minX) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX :
105         ((qAbs(maxX) - (coordX - minX)) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX;
106 
107     const qreal y = sign(targetHeight) == sign(maxY) ?
108         ((coordY - minY) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY :
109         ((qAbs(maxY) - (coordY - minY)) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY;
110 
111     return {x, y};
112 }
113 
114 template <class Stream>
formatOptions(Stream & str,unsigned options)115 static void formatOptions(Stream &str, unsigned options)
116 {
117     if (options & CXO_SYSTEM)
118         str << " CXO_SYSTEM";
119     if (options & CXO_PEN)
120         str << " CXO_PEN";
121     if (options & CXO_MESSAGES)
122         str << " CXO_MESSAGES";
123     if (options & CXO_MARGIN)
124         str << " CXO_MARGIN";
125     if (options & CXO_MGNINSIDE)
126         str << " CXO_MGNINSIDE";
127     if (options & CXO_CSRMESSAGES)
128         str << " CXO_CSRMESSAGES";
129 }
130 
131 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug d,const QWindowsTabletDeviceData & t)132 QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t)
133 {
134     QDebugStateSaver saver(d);
135     d.nospace();
136     d << "TabletDevice id:" << t.uniqueId << " pressure: " << t.minPressure
137       << ".." << t.maxPressure << " tan pressure: " << t.minTanPressure << ".."
138       << t.maxTanPressure << " area: (" << t.minX << ',' << t.minY << ',' << t.minZ
139       << ")..(" << t.maxX << ',' << t.maxY << ',' << t.maxZ << ") device "
140       << t.currentDevice << " pointer " << t.currentPointerType;
141     return d;
142 }
143 
operator <<(QDebug d,const LOGCONTEXT & lc)144 QDebug operator<<(QDebug d, const LOGCONTEXT &lc)
145 {
146     QDebugStateSaver saver(d);
147     d.nospace();
148     d << "LOGCONTEXT(\"" << QString::fromWCharArray(lc.lcName) << "\", options=0x"
149         << Qt::hex << lc.lcOptions << Qt::dec;
150     formatOptions(d, lc.lcOptions);
151     d << ", status=0x" << Qt::hex << lc.lcStatus << ", device=0x" << lc.lcDevice
152         << Qt::dec << ", PktRate=" << lc.lcPktRate
153         << ", PktData=" << lc.lcPktData << ", PktMode=" << lc.lcPktMode
154         << ", MoveMask=0x" << Qt::hex << lc.lcMoveMask << ", BtnDnMask=0x" << lc.lcBtnDnMask
155         << ", BtnUpMask=0x" << lc.lcBtnUpMask << Qt::dec << ", SysMode=" << lc.lcSysMode
156         << ", InOrg=(" << lc.lcInOrgX << ", " << lc.lcInOrgY << ", " << lc.lcInOrgZ
157         <<  "), InExt=(" << lc.lcInExtX << ", " << lc.lcInExtY << ", " << lc.lcInExtZ
158         << ") OutOrg=(" << lc.lcOutOrgX << ", " << lc.lcOutOrgY << ", "
159         << lc.lcOutOrgZ <<  "), OutExt=(" << lc.lcOutExtX << ", " << lc.lcOutExtY
160         << ", " << lc.lcOutExtZ
161         << "), Sens=(" << lc.lcSensX << ", " << lc.lcSensX << ", " << lc.lcSensZ
162         << ") SysOrg=(" << lc.lcSysOrgX << ", " << lc.lcSysOrgY
163         << "), SysExt=(" << lc.lcSysExtX << ", " << lc.lcSysExtY
164         << "), SysSens=(" << lc.lcSysSensX << ", " << lc.lcSysSensY << "))";
165     return d;
166 }
167 #endif // !QT_NO_DEBUG_STREAM
168 
169 QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL;
170 
171 /*!
172     \class QWindowsWinTab32DLL QWindowsTabletSupport
173     \brief Functions from wintabl32.dll shipped with WACOM tablets used by QWindowsTabletSupport.
174 
175     \internal
176 */
177 
init()178 bool QWindowsWinTab32DLL::init()
179 {
180     if (wTInfo)
181         return true;
182     QSystemLibrary library(QStringLiteral("wintab32"));
183     if (!library.load())
184         return false;
185     wTOpen = (PtrWTOpen)library.resolve("WTOpenW");
186     wTClose = (PtrWTClose)library.resolve("WTClose");
187     wTInfo = (PtrWTInfo)library.resolve("WTInfoW");
188     wTEnable = (PtrWTEnable)library.resolve("WTEnable");
189     wTOverlap = (PtrWTEnable)library.resolve("WTOverlap");
190     wTPacketsGet = (PtrWTPacketsGet)library.resolve("WTPacketsGet");
191     wTGet = (PtrWTGet)library.resolve("WTGetW");
192     wTQueueSizeGet = (PtrWTQueueSizeGet)library.resolve("WTQueueSizeGet");
193     wTQueueSizeSet = (PtrWTQueueSizeSet)library.resolve("WTQueueSizeSet");
194     return wTOpen && wTClose && wTInfo && wTEnable && wTOverlap && wTPacketsGet && wTQueueSizeGet && wTQueueSizeSet;
195 }
196 
197 /*!
198     \class QWindowsTabletSupport
199     \brief Tablet support for Windows.
200 
201     Support for WACOM tablets.
202 
203     \sa http://www.wacomeng.com/windows/docs/Wintab_v140.htm
204 
205     \internal
206     \since 5.2
207 */
208 
QWindowsTabletSupport(HWND window,HCTX context)209 QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context)
210     : m_window(window)
211     , m_context(context)
212 {
213      AXIS orientation[3];
214     // Some tablets don't support tilt, check if it is possible,
215     if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation))
216         m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution;
217 }
218 
~QWindowsTabletSupport()219 QWindowsTabletSupport::~QWindowsTabletSupport()
220 {
221     QWindowsTabletSupport::m_winTab32DLL.wTClose(m_context);
222     DestroyWindow(m_window);
223 }
224 
create()225 QWindowsTabletSupport *QWindowsTabletSupport::create()
226 {
227     if (!m_winTab32DLL.init())
228         return nullptr;
229     const HWND window = QWindowsContext::instance()->createDummyWindow(QStringLiteral("TabletDummyWindow"),
230                                                                        L"TabletDummyWindow",
231                                                                        qWindowsTabletSupportWndProc);
232     if (!window) {
233         qCWarning(lcQpaTablet) << __FUNCTION__ << "Unable to create window for tablet.";
234         return nullptr;
235     }
236     LOGCONTEXT lcMine;
237     // build our context from the default context
238     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine);
239     qCDebug(lcQpaTablet) << "Default: " << lcMine;
240     // Go for the raw coordinates, the tablet event will return good stuff
241     lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES;
242     lcMine.lcPktData = lcMine.lcMoveMask = PACKETDATA;
243     lcMine.lcPktMode = PacketMode;
244     lcMine.lcOutOrgX = 0;
245     lcMine.lcOutExtX = lcMine.lcInExtX;
246     lcMine.lcOutOrgY = 0;
247     lcMine.lcOutExtY = -lcMine.lcInExtY;
248     qCDebug(lcQpaTablet) << "Requesting: " << lcMine;
249     const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true);
250     if (!context) {
251         qCDebug(lcQpaTablet) << __FUNCTION__ << "Unable to open tablet.";
252         DestroyWindow(window);
253         return nullptr;
254 
255     }
256     // Set the size of the Packet Queue to the correct size
257     const int currentQueueSize = QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeGet(context);
258     if (currentQueueSize != TabletPacketQSize) {
259         if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, TabletPacketQSize)) {
260             if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, currentQueueSize))  {
261                 qWarning("Unable to set queue size on tablet. The tablet will not work.");
262                 QWindowsTabletSupport::m_winTab32DLL.wTClose(context);
263                 DestroyWindow(window);
264                 return nullptr;
265             } // cannot restore old size
266         } // cannot set
267     } // mismatch
268     qCDebug(lcQpaTablet) << "Opened tablet context " << context << " on window "
269         <<  window << "changed packet queue size " << currentQueueSize
270         << "->" <<  TabletPacketQSize << "\nobtained: " << lcMine;
271     return new QWindowsTabletSupport(window, context);
272 }
273 
options() const274 unsigned QWindowsTabletSupport::options() const
275 {
276     UINT result = 0;
277     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_CTXOPTIONS, &result);
278     return result;
279 }
280 
description() const281 QString QWindowsTabletSupport::description() const
282 {
283     const unsigned size = m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, nullptr);
284     if (!size)
285         return QString();
286     QVarLengthArray<TCHAR> winTabId(size + 1);
287     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, winTabId.data());
288     WORD implementationVersion = 0;
289     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_IMPLVERSION, &implementationVersion);
290     WORD specificationVersion = 0;
291     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_SPECVERSION, &specificationVersion);
292     const unsigned opts = options();
293     WORD devices = 0;
294     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NDEVICES, &devices);
295     WORD cursors = 0;
296     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NCURSORS, &cursors);
297     WORD extensions = 0;
298     m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NEXTENSIONS, &extensions);
299     QString result;
300     QTextStream str(&result);
301     str << '"' << QString::fromWCharArray(winTabId.data())
302         << "\" specification: v" << (specificationVersion >> 8)
303         << '.' << (specificationVersion & 0xFF) << " implementation: v"
304         << (implementationVersion >> 8) << '.' << (implementationVersion & 0xFF)
305         << ' ' << devices << " device(s), " << cursors << " cursor(s), "
306         << extensions << " extensions" << ", options: 0x" << Qt::hex << opts << Qt::dec;
307     formatOptions(str, opts);
308     if (m_tiltSupport)
309         str << " tilt";
310     return result;
311 }
312 
notifyActivate()313 void QWindowsTabletSupport::notifyActivate()
314 {
315     // Cooperate with other tablet applications, but when we get focus, I want to use the tablet.
316     const bool result = QWindowsTabletSupport::m_winTab32DLL.wTEnable(m_context, true)
317         && QWindowsTabletSupport::m_winTab32DLL.wTOverlap(m_context, true);
318    qCDebug(lcQpaTablet) << __FUNCTION__ << result;
319 }
320 
indexOfDevice(const QVector<QWindowsTabletDeviceData> & devices,qint64 uniqueId)321 static inline int indexOfDevice(const QVector<QWindowsTabletDeviceData> &devices, qint64 uniqueId)
322 {
323     for (int i = 0; i < devices.size(); ++i)
324         if (devices.at(i).uniqueId == uniqueId)
325             return i;
326     return -1;
327 }
328 
deviceType(const UINT cursorType)329 static inline QTabletEvent::TabletDevice deviceType(const UINT cursorType)
330 {
331     if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902))
332         return QTabletEvent::Stylus;
333     if (cursorType == 0x4020) // Surface Pro 2 tablet device
334         return QTabletEvent::Stylus;
335     switch (cursorType & CursorTypeBitMask) {
336     case 0x0802:
337         return QTabletEvent::Stylus;
338     case 0x0902:
339         return QTabletEvent::Airbrush;
340     case 0x0004:
341         return QTabletEvent::FourDMouse;
342     case 0x0006:
343         return QTabletEvent::Puck;
344     case 0x0804:
345         return QTabletEvent::RotationStylus;
346     default:
347         break;
348     }
349     return QTabletEvent::NoDevice;
350 }
351 
pointerType(unsigned currentCursor)352 static inline QTabletEvent::PointerType pointerType(unsigned currentCursor)
353 {
354     switch (currentCursor % 3) { // %3 for dual track
355     case 0:
356         return QTabletEvent::Cursor;
357     case 1:
358         return QTabletEvent::Pen;
359     case 2:
360         return QTabletEvent::Eraser;
361     default:
362         break;
363     }
364     return QTabletEvent::UnknownPointer;
365 }
366 
tabletInit(qint64 uniqueId,UINT cursorType) const367 QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT cursorType) const
368 {
369     QWindowsTabletDeviceData result;
370     result.uniqueId = uniqueId;
371     /* browse WinTab's many info items to discover pressure handling. */
372     AXIS axis;
373     LOGCONTEXT lc;
374     /* get the current context for its device variable. */
375     QWindowsTabletSupport::m_winTab32DLL.wTGet(m_context, &lc);
376     /* get the size of the pressure axis. */
377     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &axis);
378     result.minPressure = int(axis.axMin);
379     result.maxPressure = int(axis.axMax);
380 
381     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis);
382     result.minTanPressure = int(axis.axMin);
383     result.maxTanPressure = int(axis.axMax);
384 
385     LOGCONTEXT defaultLc;
386     /* get default region */
387     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFCONTEXT, 0, &defaultLc);
388     result.maxX = int(defaultLc.lcInExtX) - int(defaultLc.lcInOrgX);
389     result.maxY = int(defaultLc.lcInExtY) - int(defaultLc.lcInOrgY);
390     result.maxZ = int(defaultLc.lcInExtZ) - int(defaultLc.lcInOrgZ);
391     result.currentDevice = deviceType(cursorType);
392     return result;
393 }
394 
translateTabletProximityEvent(WPARAM,LPARAM lParam)395 bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam)
396 {
397     PACKET proximityBuffer[1]; // we are only interested in the first packet in this case
398     const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer);
399 
400     if (!LOWORD(lParam)) {
401         qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice;
402         if (m_currentDevice < 0 || m_currentDevice >= m_devices.size()) // QTBUG-65120, spurious leave observed
403             return false;
404         m_state = PenUp;
405         if (totalPacks > 0) {
406             QWindowSystemInterface::handleTabletLeaveProximityEvent(proximityBuffer[0].pkTime,
407                                                                     m_devices.at(m_currentDevice).currentDevice,
408                                                                     m_devices.at(m_currentDevice).currentPointerType,
409                                                                     m_devices.at(m_currentDevice).uniqueId);
410         } else {
411             QWindowSystemInterface::handleTabletLeaveProximityEvent(m_devices.at(m_currentDevice).currentDevice,
412                                                                     m_devices.at(m_currentDevice).currentPointerType,
413                                                                     m_devices.at(m_currentDevice).uniqueId);
414 
415         }
416         return true;
417     }
418 
419     if (!totalPacks)
420         return false;
421 
422     const UINT currentCursor = proximityBuffer[0].pkCursor;
423     UINT physicalCursorId;
424     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId);
425     UINT cursorType;
426     QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_TYPE, &cursorType);
427     const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId);
428     // initializing and updating the cursor should be done in response to
429     // WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send
430     // the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES
431     m_currentDevice = indexOfDevice(m_devices, uniqueId);
432     if (m_currentDevice < 0) {
433         m_currentDevice = m_devices.size();
434         m_devices.push_back(tabletInit(uniqueId, cursorType));
435     } else {
436         // The user can switch pressure sensitivity level in the driver,which
437         // will make our saved values invalid (this option is provided by Wacom
438         // drivers for compatibility reasons, and it can be adjusted on the fly)
439         m_devices[m_currentDevice] = tabletInit(uniqueId, cursorType);
440     }
441 
442     /**
443      * We should check button map for changes on every proximity event, not
444      * only during initialization phase.
445      *
446      * WARNING: in 2016 there were some Wacom table drivers, which could mess up
447      *          button mapping if the remapped button was pressed, while the
448      *          application **didn't have input focus**. This bug is somehow
449      *          related to the fact that Wacom drivers allow user to configure
450      *          per-application button-mappings. If the bug shows up again,
451      *          just move this button-map fetching into initialization block.
452      *
453      *          See https://bugs.kde.org/show_bug.cgi?id=359561
454      */
455     BYTE logicalButtons[32];
456     memset(logicalButtons, 0, 32);
457     m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_SYSBTNMAP, &logicalButtons);
458     m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0];
459     m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1];
460     m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2];
461 
462     m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor);
463     m_state = PenProximity;
464     qCDebug(lcQpaTablet) << "enter proximity for device #"
465         << m_currentDevice << m_devices.at(m_currentDevice);
466     QWindowSystemInterface::handleTabletEnterProximityEvent(proximityBuffer[0].pkTime,
467                                                             m_devices.at(m_currentDevice).currentDevice,
468                                                             m_devices.at(m_currentDevice).currentPointerType,
469                                                             m_devices.at(m_currentDevice).uniqueId);
470     return true;
471 }
472 
buttonValueToEnum(DWORD button,const QWindowsTabletDeviceData & tdd)473 Qt::MouseButton buttonValueToEnum(DWORD button,
474                                   const QWindowsTabletDeviceData &tdd) {
475 
476     enum : unsigned {
477         leftButtonValue = 0x1,
478         middleButtonValue = 0x2,
479         rightButtonValue = 0x4,
480         doubleClickButtonValue = 0x7
481     };
482 
483     button = tdd.buttonsMap.value(button);
484 
485     return button == leftButtonValue ? Qt::LeftButton :
486         button == rightButtonValue ? Qt::RightButton :
487         button == doubleClickButtonValue ? Qt::MiddleButton :
488         button == middleButtonValue ? Qt::MiddleButton :
489         button ? Qt::LeftButton /* fallback item */ :
490         Qt::NoButton;
491 }
492 
convertTabletButtons(DWORD btnNew,const QWindowsTabletDeviceData & tdd)493 Qt::MouseButtons convertTabletButtons(DWORD btnNew,
494                                       const QWindowsTabletDeviceData &tdd) {
495 
496     Qt::MouseButtons buttons = Qt::NoButton;
497     for (unsigned int i = 0; i < 3; i++) {
498         unsigned int btn = 0x1 << i;
499 
500         if (btn & btnNew) {
501             Qt::MouseButton convertedButton =
502                 buttonValueToEnum(btn, tdd);
503 
504             buttons |= convertedButton;
505 
506             /**
507              * If a button that is present in hardware input is
508              * mapped to a Qt::NoButton, it means that it is going
509              * to be eaten by the driver, for example by its
510              * "Pan/Scroll" feature. Therefore we shouldn't handle
511              * any of the events associated to it. We'll just return
512              * Qt::NoButtons here.
513              */
514         }
515     }
516     return buttons;
517 }
518 
translateTabletPacketEvent()519 bool QWindowsTabletSupport::translateTabletPacketEvent()
520 {
521     static PACKET localPacketBuf[TabletPacketQSize];  // our own tablet packet queue.
522     const int packetCount = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, TabletPacketQSize, &localPacketBuf);
523     if (!packetCount || m_currentDevice < 0)
524         return false;
525 
526     const int currentDevice = m_devices.at(m_currentDevice).currentDevice;
527     const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId;
528 
529     // The tablet can be used in 2 different modes (reflected in enum Mode),
530     // depending on its settings:
531     // 1) Absolute (pen) mode:
532     //    The coordinates are scaled to the virtual desktop (by default). The user
533     //    can also choose to scale to the monitor or a region of the screen.
534     //    When entering proximity, the tablet driver snaps the mouse pointer to the
535     //    tablet position scaled to that area and keeps it in sync.
536     // 2) Relative (mouse) mode:
537     //    The pen follows the mouse. The constant 'absoluteRange' specifies the
538     //    manhattanLength difference for detecting if a tablet input device is in this mode,
539     //    in which case we snap the position to the mouse position.
540     // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext
541     // area is always the virtual desktop.
542     const QRect virtualDesktopArea =
543         QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle());
544 
545     if (QWindowsContext::verbose > 1)  {
546         qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount
547             << "mode=" << m_mode << "target:"
548             << QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target;
549     }
550 
551     const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
552 
553     for (int i = 0; i < packetCount ; ++i) {
554         const PACKET &packet = localPacketBuf[i];
555 
556         const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0;
557 
558         const auto currentPointer = m_devices.at(m_currentDevice).currentPointerType;
559         const auto packetPointerType = pointerType(packet.pkCursor);
560 
561         const Qt::MouseButtons buttons =
562             convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice));
563 
564         if (buttons == Qt::NoButton && packetPointerType != currentPointer) {
565 
566             QWindowSystemInterface::handleTabletLeaveProximityEvent(packet.pkTime,
567                                                                     int(currentDevice),
568                                                                     int(currentPointer),
569                                                                     uniqueId);
570 
571             m_devices[m_currentDevice].currentPointerType = packetPointerType;
572 
573             QWindowSystemInterface::handleTabletEnterProximityEvent(packet.pkTime,
574                                                                     int(currentDevice),
575                                                                     int(packetPointerType),
576                                                                     uniqueId);
577         }
578 
579         QPointF globalPosF =
580             m_devices.at(m_currentDevice).scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea);
581 
582         QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
583 
584         // Get Mouse Position and compare to tablet info
585         const QPoint mouseLocation = QWindowsCursor::mousePosition();
586         if (m_state == PenProximity) {
587             m_state = PenDown;
588             m_mode = (mouseLocation - globalPosF).manhattanLength() > m_absoluteRange
589                 ? MouseMode : PenMode;
590             qCDebug(lcQpaTablet) << __FUNCTION__ << "mode=" << m_mode << "pen:"
591                 << globalPosF << "mouse:" << mouseLocation;
592         }
593         if (m_mode == MouseMode)
594             globalPosF = mouseLocation;
595         const QPoint globalPos = globalPosF.toPoint();
596 
597         if (!target)
598             target = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT);
599         if (!target)
600             continue;
601 
602         const QPlatformWindow *platformWindow = target->handle();
603         Q_ASSERT(platformWindow);
604         const QPoint localPos = platformWindow->mapFromGlobal(globalPos);
605 
606         const qreal pressureNew = packet.pkButtons && (currentPointer == QTabletEvent::Pen || currentPointer == QTabletEvent::Eraser) ?
607             m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) :
608             qreal(0);
609         const qreal tangentialPressure = currentDevice == QTabletEvent::Airbrush ?
610             m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) :
611             qreal(0);
612 
613         int tiltX = 0;
614         int tiltY = 0;
615         qreal rotation = 0;
616         if (m_tiltSupport) {
617             // Convert from azimuth and altitude to x tilt and y tilt. What
618             // follows is the optimized version. Here are the equations used:
619             // X = sin(azimuth) * cos(altitude)
620             // Y = cos(azimuth) * cos(altitude)
621             // Z = sin(altitude)
622             // X Tilt = arctan(X / Z)
623             // Y Tilt = arctan(Y / Z)
624             const double radAzim = qDegreesToRadians(packet.pkOrientation.orAzimuth / 10.0);
625             const double tanAlt = std::tan(qDegreesToRadians(std::abs(packet.pkOrientation.orAltitude / 10.0)));
626 
627             const double radX = std::atan(std::sin(radAzim) / tanAlt);
628             const double radY = std::atan(std::cos(radAzim) / tanAlt);
629             tiltX = int(qRadiansToDegrees(radX));
630             tiltY = int(qRadiansToDegrees(-radY));
631             rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0);
632             if (rotation > 180.0)
633                 rotation -= 360.0;
634         }
635 
636         if (QWindowsContext::verbose > 1)  {
637             qCDebug(lcQpaTablet)
638                 << "Packet #" << i << '/' << packetCount << "button:" << packet.pkButtons
639                 << globalPosF << z << "to:" << target << localPos << "(packet" << packet.pkX
640                 << packet.pkY << ") dev:" << currentDevice << "pointer:"
641                 << currentPointer << "P:" << pressureNew << "tilt:" << tiltX << ','
642                 << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation;
643         }
644 
645         QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
646                                                   currentDevice, currentPointer,
647                                                   buttons,
648                                                   pressureNew, tiltX, tiltY,
649                                                   tangentialPressure, rotation, z,
650                                                   uniqueId,
651                                                   keyboardModifiers);
652     }
653     return true;
654 }
655 
656 QT_END_NAMESPACE
657