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