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