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