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
qwindowspointerhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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
4#include <QtCore/qt_windows.h>
5
7#if QT_CONFIG(tabletevent)
8# include "qwindowstabletsupport.h"
9#endif
12#include "qwindowswindow.h"
14#include "qwindowsscreen.h"
15
16#include <QtGui/qguiapplication.h>
17#include <QtGui/qscreen.h>
18#include <QtGui/qpointingdevice.h>
19#include <QtGui/qwindow.h>
20#include <QtGui/private/qguiapplication_p.h>
21#include <QtCore/qvarlengtharray.h>
22#include <QtCore/qloggingcategory.h>
23#include <QtCore/qqueue.h>
24
25#include <algorithm>
26
27#include <windowsx.h>
28
29QT_BEGIN_NAMESPACE
30
31enum {
32 QT_PT_POINTER = 1,
33 QT_PT_TOUCH = 2,
34 QT_PT_PEN = 3,
35 QT_PT_MOUSE = 4,
36 QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD
37};
38
39qint64 QWindowsPointerHandler::m_nextInputDeviceId = 1;
40
41const QPointingDevice *primaryMouse()
42{
43 static QPointer<const QPointingDevice> result;
44 if (!result)
45 result = QPointingDevice::primaryPointingDevice();
46 return result;
47}
48
52
53bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result)
54{
55 *result = 0;
56 const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
57
58 if (!GetPointerType(pointerId, &m_pointerType)) {
59 qWarning() << "GetPointerType() failed:" << qt_error_string();
60 return false;
61 }
62
63 switch (m_pointerType) {
64 case QT_PT_POINTER:
65 case QT_PT_MOUSE:
66 case QT_PT_TOUCHPAD: {
67 // Let Mouse/TouchPad be handled using legacy messages.
68 return false;
69 }
70 case QT_PT_TOUCH: {
71 quint32 pointerCount = 0;
72 if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
73 qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
74 return false;
75 }
76 QVarLengthArray<POINTER_TOUCH_INFO, 10> touchInfo(pointerCount);
77 if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
78 qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
79 return false;
80 }
81
82 if (!pointerCount)
83 return false;
84
85 // The history count is the same for all the touchpoints in touchInfo
86 quint32 historyCount = touchInfo[0].pointerInfo.historyCount;
87 // dispatch any skipped frames if event compression is disabled by the app
88 if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) {
89 touchInfo.resize(pointerCount * historyCount);
90 if (!GetPointerFrameTouchInfoHistory(pointerId,
91 &historyCount,
92 &pointerCount,
93 touchInfo.data())) {
94 qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string();
95 return false;
96 }
97
98 // history frames are returned with the most recent frame first so we iterate backwards
99 bool result = true;
100 for (auto it = touchInfo.rbegin(), end = touchInfo.rend(); it != end; it += pointerCount) {
101 result &= translateTouchEvent(window, hwnd, et, msg,
102 &(*(it + (pointerCount - 1))), pointerCount);
103 }
104 return result;
105 }
106
107 return translateTouchEvent(window, hwnd, et, msg, touchInfo.data(), pointerCount);
108 }
109 case QT_PT_PEN: {
110 POINTER_PEN_INFO penInfo;
111 if (!GetPointerPenInfo(pointerId, &penInfo)) {
112 qWarning() << "GetPointerPenInfo() failed:" << qt_error_string();
113 return false;
114 }
115
116 quint32 historyCount = penInfo.pointerInfo.historyCount;
117 // dispatch any skipped frames if generic or tablet event compression is disabled by the app
118 if (historyCount > 1
119 && (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)
120 || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) {
121 QVarLengthArray<POINTER_PEN_INFO, 10> penInfoHistory(historyCount);
122
123 if (!GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
124 qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string();
125 return false;
126 }
127
128 // history frames are returned with the most recent frame first so we iterate backwards
129 bool result = true;
130 for (auto it = penInfoHistory.rbegin(), end = penInfoHistory.rend(); it != end; ++it) {
131 result &= translatePenEvent(window, hwnd, et, msg, &(*(it)));
132 }
133 return result;
134 }
135
136 return translatePenEvent(window, hwnd, et, msg, &penInfo);
137 }
138 }
139 return false;
140}
141
142namespace {
143struct MouseEvent {
144 QEvent::Type type;
145 Qt::MouseButton button;
146};
147} // namespace
148
149static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON...
150{
151 return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton;
152}
153
154static inline MouseEvent eventFromMsg(const MSG &msg)
155{
156 switch (msg.message) {
157 case WM_MOUSEMOVE:
158 return {QEvent::MouseMove, Qt::NoButton};
159 case WM_LBUTTONDOWN:
160 return {QEvent::MouseButtonPress, Qt::LeftButton};
161 case WM_LBUTTONUP:
162 return {QEvent::MouseButtonRelease, Qt::LeftButton};
163 case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press
164 return {QEvent::MouseButtonPress, Qt::LeftButton};
165 case WM_MBUTTONDOWN:
166 return {QEvent::MouseButtonPress, Qt::MiddleButton};
167 case WM_MBUTTONUP:
168 return {QEvent::MouseButtonRelease, Qt::MiddleButton};
169 case WM_MBUTTONDBLCLK:
170 return {QEvent::MouseButtonPress, Qt::MiddleButton};
171 case WM_RBUTTONDOWN:
172 return {QEvent::MouseButtonPress, Qt::RightButton};
173 case WM_RBUTTONUP:
174 return {QEvent::MouseButtonRelease, Qt::RightButton};
175 case WM_RBUTTONDBLCLK:
176 return {QEvent::MouseButtonPress, Qt::RightButton};
177 case WM_XBUTTONDOWN:
178 return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
179 case WM_XBUTTONUP:
180 return {QEvent::MouseButtonRelease, extraButton(msg.wParam)};
181 case WM_XBUTTONDBLCLK:
182 return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
183 case WM_NCMOUSEMOVE:
184 return {QEvent::NonClientAreaMouseMove, Qt::NoButton};
185 case WM_NCLBUTTONDOWN:
186 return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
187 case WM_NCLBUTTONUP:
188 return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton};
189 case WM_NCLBUTTONDBLCLK:
190 return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
191 case WM_NCMBUTTONDOWN:
192 return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
193 case WM_NCMBUTTONUP:
194 return {QEvent::NonClientAreaMouseButtonRelease, Qt::MiddleButton};
195 case WM_NCMBUTTONDBLCLK:
196 return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
197 case WM_NCRBUTTONDOWN:
198 return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
199 case WM_NCRBUTTONUP:
200 return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton};
201 case WM_NCRBUTTONDBLCLK:
202 return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
203 default: // WM_MOUSELEAVE
204 break;
205 }
206 return {QEvent::None, Qt::NoButton};
207}
208
210{
211 Qt::MouseButtons result = Qt::NoButton;
212 if (keyState & MK_LBUTTON)
213 result |= Qt::LeftButton;
214 if (keyState & MK_RBUTTON)
215 result |= Qt::RightButton;
216 if (keyState & MK_MBUTTON)
217 result |= Qt::MiddleButton;
218 if (keyState & MK_XBUTTON1)
219 result |= Qt::XButton1;
220 if (keyState & MK_XBUTTON2)
221 result |= Qt::XButton2;
222 return result;
223}
224
225Qt::MouseButtons QWindowsPointerHandler::queryMouseButtons()
226{
227 Qt::MouseButtons result = Qt::NoButton;
228 const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON);
229 if (GetAsyncKeyState(VK_LBUTTON) < 0)
230 result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton;
231 if (GetAsyncKeyState(VK_RBUTTON) < 0)
232 result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton;
233 if (GetAsyncKeyState(VK_MBUTTON) < 0)
234 result |= Qt::MiddleButton;
235 if (GetAsyncKeyState(VK_XBUTTON1) < 0)
236 result |= Qt::XButton1;
237 if (GetAsyncKeyState(VK_XBUTTON2) < 0)
238 result |= Qt::XButton2;
239 return result;
240}
241
242static QWindow *getWindowUnderPointer(QWindow *window, QPoint globalPos)
243{
244 QWindowsWindow *platformWindow = static_cast<QWindowsWindow *>(window->handle());
245
246 QWindow *currentWindowUnderPointer = platformWindow->hasMouseCapture() ?
247 QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window;
248
249 while (currentWindowUnderPointer && currentWindowUnderPointer->flags() & Qt::WindowTransparentForInput)
250 currentWindowUnderPointer = currentWindowUnderPointer->parent();
251
252 // QTBUG-44332: When Qt is running at low integrity level and
253 // a Qt Window is parented on a Window of a higher integrity process
254 // using QWindow::fromWinId() (for example, Qt running in a browser plugin)
255 // ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED)
256 if (!currentWindowUnderPointer) {
257 const QRect clientRect(QPoint(0, 0), window->size());
258 if (clientRect.contains(globalPos))
259 currentWindowUnderPointer = window;
260 }
261 return currentWindowUnderPointer;
262}
263
264static bool trackLeave(HWND hwnd)
265{
266 TRACKMOUSEEVENT tme;
267 tme.cbSize = sizeof(TRACKMOUSEEVENT);
268 tme.dwFlags = TME_LEAVE;
269 tme.hwndTrack = hwnd;
270 tme.dwHoverTime = HOVER_DEFAULT;
271 return TrackMouseEvent(&tme);
272}
273
274static bool isValidWheelReceiver(QWindow *candidate)
275{
276 if (candidate) {
277 const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate);
278 if (toplevel->handle() && toplevel->handle()->isForeignWindow())
279 return true;
280 if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel))
282 }
283 return false;
284}
285
286QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::createTouchDevice(bool mouseEmulation)
287{
288 const int digitizers = GetSystemMetrics(SM_DIGITIZER);
289 if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH)))
290 return nullptr;
291 const int tabletPc = GetSystemMetrics(SM_TABLETPC);
292 const int maxTouchPoints = GetSystemMetrics(SM_MAXIMUMTOUCHES);
293 const QPointingDevice::DeviceType type = (digitizers & NID_INTEGRATED_TOUCH)
294 ? QInputDevice::DeviceType::TouchScreen : QInputDevice::DeviceType::TouchPad;
295 QInputDevice::Capabilities capabilities = QInputDevice::Capability::Position
296 | QInputDevice::Capability::Area
297 | QInputDevice::Capability::NormalizedPosition;
298 if (type != QInputDevice::DeviceType::TouchScreen) {
299 capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
300 capabilities.setFlag(QInputDevice::Capability::Scroll);
301 } else if (mouseEmulation) {
302 capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
303 }
304
305 const int flags = digitizers & ~NID_READY;
306 qCDebug(lcQpaEvents) << "Digitizers:" << Qt::hex << Qt::showbase << flags
307 << "Ready:" << (digitizers & NID_READY) << Qt::dec << Qt::noshowbase
308 << "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints << "Capabilities:" << capabilities;
309
310 const int buttonCount = type == QInputDevice::DeviceType::TouchScreen ? 1 : 3;
311 // TODO: use system-provided name and device ID rather than empty-string and m_nextInputDeviceId
312 const qint64 systemId = m_nextInputDeviceId++ | (qint64(flags << 2));
313 auto d = new QPointingDevice(QString(), systemId, type,
314 QPointingDevice::PointerType::Finger,
315 capabilities, maxTouchPoints, buttonCount,
316 QString(), QPointingDeviceUniqueId::fromNumericId(systemId));
317 return QPointingDevicePtr(d);
318}
319
321{
322 m_lastEventType = QEvent::None;
323 m_lastEventButton = Qt::NoButton;
324}
325
326void QWindowsPointerHandler::handleCaptureRelease(QWindow *window,
327 QWindow *currentWindowUnderPointer,
328 HWND hwnd,
329 QEvent::Type eventType,
330 Qt::MouseButtons mouseButtons)
331{
332 auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
333
334 // Qt expects the platform plugin to capture the mouse on any button press until release.
335 if (!platformWindow->hasMouseCapture() && eventType == QEvent::MouseButtonPress) {
336
337 platformWindow->setMouseGrabEnabled(true);
338 platformWindow->setFlag(QWindowsWindow::AutoMouseCapture);
339 qCDebug(lcQpaEvents) << "Automatic mouse capture " << window;
340
341 // Implement "Click to focus" for native child windows (unless it is a native widget window).
342 if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window)
343 window->requestActivate();
344
345 } else if (platformWindow->hasMouseCapture()
346 && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)
347 && eventType == QEvent::MouseButtonRelease
348 && !mouseButtons) {
349
350 platformWindow->setMouseGrabEnabled(false);
351 qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window;
352 }
353
354 // Enter new window: track to generate leave event.
355 // If there is an active capture, only track if the current window is capturing,
356 // so we don't get extra leave when cursor leaves the application.
357 if (window != m_currentWindow &&
358 (!platformWindow->hasMouseCapture() || currentWindowUnderPointer == window)) {
359 trackLeave(hwnd);
360 m_currentWindow = window;
361 }
362}
363
364void QWindowsPointerHandler::handleEnterLeave(QWindow *window,
365 QWindow *currentWindowUnderPointer,
366 QPoint globalPos)
367{
368 auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
369 const bool hasCapture = platformWindow->hasMouseCapture();
370
371 // No enter or leave events are sent as long as there is an autocapturing window.
372 if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
373
374 // Leave is needed if:
375 // 1) There is no capture and we move from a window to another window.
376 // Note: Leaving the application entirely is handled in translateMouseEvent(WM_MOUSELEAVE).
377 // 2) There is capture and we move out of the capturing window.
378 // 3) There is a new capture and we were over another window.
379 if ((m_windowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
380 && (!hasCapture || window == m_windowUnderPointer))
381 || (hasCapture && m_previousCaptureWindow != window && m_windowUnderPointer
382 && m_windowUnderPointer != window)) {
383
384 qCDebug(lcQpaEvents) << "Leaving window " << m_windowUnderPointer;
385 QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
386
387 if (hasCapture && currentWindowUnderPointer != window) {
388 // Clear tracking if capturing and current window is not the capturing window
389 // to avoid leave when mouse actually leaves the application.
390 m_currentWindow = nullptr;
391 // We are not officially in any window, but we need to set some cursor to clear
392 // whatever cursor the left window had, so apply the cursor of the capture window.
393 platformWindow->applyCursor();
394 }
395 }
396
397 // Enter is needed if:
398 // 1) There is no capture and we move to a new window.
399 // 2) There is capture and we move into the capturing window.
400 // 3) The capture just ended and we are over non-capturing window.
401 if ((currentWindowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
402 && (!hasCapture || currentWindowUnderPointer == window))
403 || (m_previousCaptureWindow && !hasCapture && currentWindowUnderPointer
404 && currentWindowUnderPointer != m_previousCaptureWindow)) {
405
406 QPoint wumLocalPos;
407 if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) {
408 wumLocalPos = wumPlatformWindow->mapFromGlobal(globalPos);
409 wumPlatformWindow->applyCursor();
410 }
411 qCDebug(lcQpaEvents) << "Entering window " << currentWindowUnderPointer;
412 QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, wumLocalPos, globalPos);
413 }
414
415 // We need to track m_windowUnderPointer separately from m_currentWindow, as Windows
416 // mouse tracking will not trigger WM_MOUSELEAVE for leaving window when mouse capture is set.
417 m_windowUnderPointer = currentWindowUnderPointer;
418 }
419
420 m_previousCaptureWindow = hasCapture ? window : nullptr;
421}
422
423bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
424 QtWindows::WindowsEventType et,
425 MSG msg, PVOID vTouchInfo, quint32 count)
426{
427 Q_UNUSED(hwnd);
428
429 auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
430
431 if (et & QtWindows::NonClientEventFlag)
432 return false; // Let DefWindowProc() handle Non Client messages.
433
434 if (count < 1)
435 return false;
436
437 if (msg.message == WM_POINTERCAPTURECHANGED) {
438 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
439 QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(),
440 keyMapper->queryKeyboardModifiers());
441 m_lastTouchPoints.clear();
442 return true;
443 }
444
445 if (msg.message == WM_POINTERLEAVE) {
446 for (quint32 i = 0; i < count; ++i) {
447 const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
448 int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
449 if (id != -1)
450 m_lastTouchPoints.remove(id);
451 }
452 // Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
453 QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
454 }
455
456 // Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
457 if (msg.message > WM_POINTERUP)
458 return false;
459
460 const QScreen *screen = window->screen();
461 if (!screen)
462 screen = QGuiApplication::primaryScreen();
463 if (!screen)
464 return false;
465
466 const QRect screenGeometry = screen->geometry();
467
468 QList<QWindowSystemInterface::TouchPoint> touchPoints;
469
470 if (QWindowsContext::verbose > 1)
471 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
472 << __FUNCTION__
473 << " message=" << Qt::hex << msg.message
474 << " count=" << Qt::dec << count;
475
476 QEventPoint::States allStates;
477 QSet<int> inputIds;
478
479 for (quint32 i = 0; i < count; ++i) {
480 if (QWindowsContext::verbose > 1)
481 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
482 << " TouchPoint id=" << touchInfo[i].pointerInfo.pointerId
483 << " frame=" << touchInfo[i].pointerInfo.frameId
484 << " flags=" << Qt::hex << touchInfo[i].pointerInfo.pointerFlags;
485
486 QWindowSystemInterface::TouchPoint touchPoint;
487 const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
488 int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
489 if (id == -1) {
490 // Start tracking after fingers touch the screen. Ignore bogus updates after touch is released.
491 if ((touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) == 0)
492 continue;
493 id = m_touchInputIDToTouchPointID.size();
494 m_touchInputIDToTouchPointID.insert(pointerId, id);
495 }
496 touchPoint.id = id;
497 touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ?
498 touchInfo[i].pressure / 1024.0 : 1.0;
499 if (m_lastTouchPoints.contains(touchPoint.id))
500 touchPoint.normalPosition = m_lastTouchPoints.value(touchPoint.id).normalPosition;
501
502 const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x,
503 touchInfo[i].pointerInfo.ptPixelLocation.y);
504
505 if (touchInfo[i].touchMask & TOUCH_MASK_CONTACTAREA)
506 touchPoint.area.setSize(QSizeF(touchInfo[i].rcContact.right - touchInfo[i].rcContact.left,
507 touchInfo[i].rcContact.bottom - touchInfo[i].rcContact.top));
508 touchPoint.area.moveCenter(screenPos);
509 QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(),
510 screenPos.y() / screenGeometry.height());
511 const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
512 touchPoint.normalPosition = normalPosition;
513
514 if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
515 touchPoint.state = QEventPoint::State::Pressed;
516 m_lastTouchPoints.insert(touchPoint.id, touchPoint);
517 } else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) {
518 touchPoint.state = QEventPoint::State::Released;
519 m_lastTouchPoints.remove(touchPoint.id);
520 } else {
521 touchPoint.state = stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated;
522 m_lastTouchPoints.insert(touchPoint.id, touchPoint);
523 }
524 allStates |= touchPoint.state;
525
526 touchPoints.append(touchPoint);
527 inputIds.insert(touchPoint.id);
528
529 // Avoid getting repeated messages for this frame if there are multiple pointerIds
530 SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
531 }
532
533 // Some devices send touches for each finger in a different message/frame, instead of consolidating
534 // them in the same frame as we were expecting. We account for missing unreleased touches here.
535 for (auto tp : std::as_const(m_lastTouchPoints)) {
536 if (!inputIds.contains(tp.id)) {
537 tp.state = QEventPoint::State::Stationary;
538 allStates |= tp.state;
539 touchPoints.append(tp);
540 }
541 }
542
543 if (touchPoints.count() == 0)
544 return false;
545
546 // all touch points released, forget the ids we've seen.
547 if (allStates == QEventPoint::State::Released)
548 m_touchInputIDToTouchPointID.clear();
549
550 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
551 QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints,
552 keyMapper->queryKeyboardModifiers());
553 return false; // Allow mouse messages to be generated.
554}
555
556#if QT_CONFIG(tabletevent)
557QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::findTabletDevice(QPointingDevice::PointerType pointerType) const
558{
559 for (const auto &d : m_tabletDevices) {
560 if (d->pointerType() == pointerType)
561 return d;
562 }
563 return {};
564}
565#endif
566
567bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et,
568 MSG msg, PVOID vPenInfo)
569{
570#if QT_CONFIG(tabletevent)
571 if (et & QtWindows::NonClientEventFlag)
572 return false; // Let DefWindowProc() handle Non Client messages.
573
574 auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
575
576 RECT pRect, dRect;
577 if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
578 return false;
579
580 const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
581 const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y);
582 const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos);
583 const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left)
584 / (pRect.right - pRect.left) * (dRect.right - dRect.left),
585 dRect.top + qreal(penInfo->pointerInfo.ptHimetricLocation.y - pRect.top)
586 / (pRect.bottom - pRect.top) * (dRect.bottom - dRect.top));
587 const bool hasPressure = (penInfo->penMask & PEN_MASK_PRESSURE) != 0;
588 const bool hasRotation = (penInfo->penMask & PEN_MASK_ROTATION) != 0;
589 const qreal pressure = hasPressure ? qreal(penInfo->pressure) / 1024.0 : 0.5;
590 const qreal rotation = hasRotation ? qreal(penInfo->rotation) : 0.0;
591 const qreal tangentialPressure = 0.0;
592 const bool hasTiltX = (penInfo->penMask & PEN_MASK_TILT_X) != 0;
593 const bool hasTiltY = (penInfo->penMask & PEN_MASK_TILT_Y) != 0;
594 const int xTilt = hasTiltX ? penInfo->tiltX : 0;
595 const int yTilt = hasTiltY ? penInfo->tiltY : 0;
596 const int z = 0;
597
598 if (QWindowsContext::verbose > 1)
599 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
600 << __FUNCTION__ << " systemId=" << systemId
601 << " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos
602 << " message=" << Qt::hex << msg.message
603 << " flags=" << Qt::hex << penInfo->pointerInfo.pointerFlags;
604
605 QPointingDevice::PointerType type;
606 // Since it may be the middle button, so if the checks fail then it should
607 // be set to Middle if it was used.
608 Qt::MouseButtons mouseButtons = queryMouseButtons();
609
610 const bool pointerInContact = IS_POINTER_INCONTACT_WPARAM(msg.wParam);
611 if (pointerInContact)
612 mouseButtons = Qt::LeftButton;
613
614 if (penInfo->penFlags & (PEN_FLAG_ERASER | PEN_FLAG_INVERTED)) {
615 type = QPointingDevice::PointerType::Eraser;
616 } else {
617 type = QPointingDevice::PointerType::Pen;
618 if (pointerInContact && penInfo->penFlags & PEN_FLAG_BARREL)
619 mouseButtons = Qt::RightButton; // Either left or right, not both
620 }
621
622 auto device = findTabletDevice(type);
623 if (device.isNull()) {
624 QInputDevice::Capabilities caps(QInputDevice::Capability::Position
625 | QInputDevice::Capability::MouseEmulation
626 | QInputDevice::Capability::Hover);
627 if (hasPressure)
628 caps |= QInputDevice::Capability::Pressure;
629 if (hasRotation)
630 caps |= QInputDevice::Capability::Rotation;
631 if (hasTiltX)
632 caps |= QInputDevice::Capability::XTilt;
633 if (hasTiltY)
634 caps |= QInputDevice::Capability::YTilt;
635 const qint64 uniqueId = systemId | (qint64(type) << 32L);
636 device.reset(new QPointingDevice(QStringLiteral("wmpointer"),
637 systemId, QInputDevice::DeviceType::Stylus,
638 type, caps, 1, 3, QString(),
639 QPointingDeviceUniqueId::fromNumericId(uniqueId)));
640 QWindowSystemInterface::registerInputDevice(device.data());
641 m_tabletDevices.append(device);
642 }
643
644 const auto uniqueId = device->uniqueId().numericId();
645 m_activeTabletDevice = device;
646
647 switch (msg.message) {
648 case WM_POINTERENTER: {
649 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), true);
650 m_windowUnderPointer = window;
651 // The local coordinates may fall outside the window.
652 // Wait until the next update to send the enter event.
653 m_needsEnterOnPointerUpdate = true;
654 break;
655 }
656 case WM_POINTERLEAVE:
657 if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) {
658 QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
659 m_windowUnderPointer = nullptr;
660 m_currentWindow = nullptr;
661 }
662 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), false);
663 break;
664 case WM_POINTERDOWN:
665 case WM_POINTERUP:
666 case WM_POINTERUPDATE: {
667 QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
668 if (!target && m_windowUnderPointer)
669 target = m_windowUnderPointer;
670 if (!target)
671 target = window;
672
673 if (m_needsEnterOnPointerUpdate) {
674 m_needsEnterOnPointerUpdate = false;
675 if (window != m_currentWindow) {
676 // make sure we subscribe to leave events for this window
677 trackLeave(hwnd);
678
679 QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos);
680 m_currentWindow = window;
681 if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target))
682 wumPlatformWindow->applyCursor();
683 }
684 }
685 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
686 const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
687
688 QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(),
689 localPos, hiResGlobalPos, mouseButtons,
690 pressure, xTilt, yTilt, tangentialPressure,
691 rotation, z, keyModifiers);
692 return false; // Allow mouse messages to be generated.
693 }
694 }
695 return true;
696#else
697 Q_UNUSED(window);
698 Q_UNUSED(hwnd);
699 Q_UNUSED(et);
700 Q_UNUSED(msg);
701 Q_UNUSED(vPenInfo);
702 return false;
703#endif
704}
705
707{
708 // For details, see
709 // https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages
710 const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
711 const LONG_PTR MI_WP_SIGNATURE = 0xFF515700;
712
713 return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE);
714}
715
716bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
717 QWindow *currentWindowUnderPointer,
718 MSG msg,
719 QPoint globalPos,
720 Qt::KeyboardModifiers keyModifiers)
721{
722 QWindow *receiver = currentWindowUnderPointer;
723 if (!isValidWheelReceiver(receiver))
724 receiver = window;
725 if (!isValidWheelReceiver(receiver))
726 return true;
727
728 int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam);
729
730 // Qt horizontal wheel rotation orientation is opposite to the one in WM_MOUSEHWHEEL
731 if (msg.message == WM_MOUSEHWHEEL)
732 delta = -delta;
733
734 const QPoint angleDelta = (msg.message == WM_MOUSEHWHEEL || (keyModifiers & Qt::AltModifier)) ?
735 QPoint(delta, 0) : QPoint(0, delta);
736
737 QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
738
739 QWindowSystemInterface::handleWheelEvent(receiver, msg.time, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
740 return true;
741}
742
743// Process legacy mouse messages here.
745 HWND hwnd,
746 QtWindows::WindowsEventType et,
747 MSG msg,
748 LRESULT *result)
749{
750 *result = 0;
751
752 QPoint localPos;
753 QPoint globalPos;
754 QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
755
756 if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
757 globalPos = eventPos;
758 localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
759 } else {
760 if (QWindowsBaseWindow::isRtlLayout(hwnd)) {
761 RECT clientArea;
762 GetClientRect(hwnd, &clientArea);
763 eventPos.setX(clientArea.right - eventPos.x());
764 }
765
766 globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
767 auto targetHwnd = hwnd;
768 if (auto *pw = window->handle())
769 targetHwnd = HWND(pw->winId());
770 localPos = targetHwnd == hwnd
771 ? eventPos
772 : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
773 }
774
775 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
776 const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
777 QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
778
779 if (et == QtWindows::MouseWheelEvent)
780 return translateMouseWheelEvent(window, currentWindowUnderPointer, msg, globalPos, keyModifiers);
781
782 // Windows sends a mouse move with no buttons pressed to signal "Enter"
783 // when a window is shown over the cursor. Discard the event and only use
784 // it for generating QEvent::Enter to be consistent with other platforms -
785 // X11 and macOS.
786 bool discardEvent = false;
787 if (msg.message == WM_MOUSEMOVE) {
788 Q_CONSTINIT static QPoint lastMouseMovePos;
789 if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos))
790 discardEvent = true;
791 lastMouseMovePos = globalPos;
792 }
793
794 Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
795 const QPointingDevice *device = primaryMouse();
796
797 // Following the logic of the old mouse handler, only events synthesized
798 // for touch screen are marked as such. On some systems, using the bit 7 of
799 // the extra msg info for checking if synthesized for touch does not work,
800 // so we use the pointer type of the last pointer message.
802 switch (m_pointerType) {
803 case QT_PT_TOUCH:
804 if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)
805 return false;
806 source = Qt::MouseEventSynthesizedBySystem;
807 if (!m_touchDevice.isNull())
808 device = m_touchDevice.data();
809 break;
810 case QT_PT_PEN:
811#if QT_CONFIG(tabletevent)
812 qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
813 return false;
814#endif
815 break;
816 }
817 }
818
819 const MouseEvent mouseEvent = eventFromMsg(msg);
820 Qt::MouseButtons mouseButtons;
821
822 if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
823 mouseButtons = queryMouseButtons();
824 else
825 mouseButtons = mouseButtonsFromKeyState(msg.wParam);
826
827 // When the left/right mouse buttons are pressed over the window title bar
828 // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP
829 // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE.
830 // We detect it and generate the missing release events here. (QTBUG-75678)
831 // The last event vars are cleared on QWindowsContext::handleExitSizeMove()
832 // to avoid generating duplicated release events.
833 if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
834 && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
835 && (m_lastEventButton & mouseButtons) == 0) {
836 auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
837 QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
838 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
839 releaseType, keyModifiers, source);
840 }
841 m_lastEventType = mouseEvent.type;
842 m_lastEventButton = mouseEvent.button;
843
844 if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
845 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
846 mouseEvent.button, mouseEvent.type, keyModifiers, source);
847 return false; // Allow further event processing
848 }
849
850 if (msg.message == WM_MOUSELEAVE) {
851 if (window == m_currentWindow) {
852 QWindow *leaveTarget = m_windowUnderPointer ? m_windowUnderPointer : m_currentWindow;
853 qCDebug(lcQpaEvents) << "Leaving window " << leaveTarget;
854 QWindowSystemInterface::handleLeaveEvent(leaveTarget);
855 m_windowUnderPointer = nullptr;
856 m_currentWindow = nullptr;
857 }
858 return true;
859 }
860
861 handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons);
862 handleEnterLeave(window, currentWindowUnderPointer, globalPos);
863
864 if (!discardEvent && mouseEvent.type != QEvent::None) {
865 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
866 mouseEvent.button, mouseEvent.type, keyModifiers, source);
867 }
868
869 // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
870 // is sent for unhandled WM_XBUTTONDOWN.
871 return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
872 || QWindowSystemInterface::flushWindowSystemEvents();
873}
874
875QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
Singleton container for all relevant information.
QPlatformKeyMapper * keyMapper() const
static QWindowsContext * instance()
bool translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result)
bool translateMouseEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result)
Windows screen.
static QWindow * windowAt(const QPoint &point, unsigned flags)
Raster or OpenGL Window.
bool testFlag(unsigned f) const
void applyCursor()
Applies to cursor property set on the window to the global cursor.
bool hasMouseCapture() const
WindowsEventType
Enumerations for WM_XX events.
#define WM_POINTERCAPTURECHANGED
#define WM_POINTERLEAVE
#define WM_POINTERUP
static bool isValidWheelReceiver(QWindow *candidate)
static bool trackLeave(HWND hwnd)
static QWindow * getWindowUnderPointer(QWindow *window, QPoint globalPos)
static Qt::MouseButton extraButton(WPARAM wParam)
const QPointingDevice * primaryMouse()
static MouseEvent eventFromMsg(const MSG &msg)
static bool isMouseEventSynthesizedFromPenOrTouch()
static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState)