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 } else if (platformWindow->hasMouseCapture()
343 && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)
344 && eventType == QEvent::MouseButtonRelease
345 && !mouseButtons) {
346
347 platformWindow->setMouseGrabEnabled(false);
348 qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window;
349 }
350
351 // Enter new window: track to generate leave event.
352 // If there is an active capture, only track if the current window is capturing,
353 // so we don't get extra leave when cursor leaves the application.
354 if (window != m_currentWindow &&
355 (!platformWindow->hasMouseCapture() || currentWindowUnderPointer == window)) {
356 trackLeave(hwnd);
357 m_currentWindow = window;
358 }
359}
360
361void QWindowsPointerHandler::handleEnterLeave(QWindow *window,
362 QWindow *currentWindowUnderPointer,
363 QPoint globalPos)
364{
365 auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
366 const bool hasCapture = platformWindow->hasMouseCapture();
367
368 // No enter or leave events are sent as long as there is an autocapturing window.
369 if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
370
371 // Leave is needed if:
372 // 1) There is no capture and we move from a window to another window.
373 // Note: Leaving the application entirely is handled in translateMouseEvent(WM_MOUSELEAVE).
374 // 2) There is capture and we move out of the capturing window.
375 // 3) There is a new capture and we were over another window.
376 if ((m_windowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
377 && (!hasCapture || window == m_windowUnderPointer))
378 || (hasCapture && m_previousCaptureWindow != window && m_windowUnderPointer
379 && m_windowUnderPointer != window)) {
380
381 qCDebug(lcQpaEvents) << "Leaving window " << m_windowUnderPointer;
382 QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
383
384 if (hasCapture && currentWindowUnderPointer != window) {
385 // Clear tracking if capturing and current window is not the capturing window
386 // to avoid leave when mouse actually leaves the application.
387 m_currentWindow = nullptr;
388 // We are not officially in any window, but we need to set some cursor to clear
389 // whatever cursor the left window had, so apply the cursor of the capture window.
390 platformWindow->applyCursor();
391 }
392 }
393
394 // Enter is needed if:
395 // 1) There is no capture and we move to a new window.
396 // 2) There is capture and we move into the capturing window.
397 // 3) The capture just ended and we are over non-capturing window.
398 if ((currentWindowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
399 && (!hasCapture || currentWindowUnderPointer == window))
400 || (m_previousCaptureWindow && !hasCapture && currentWindowUnderPointer
401 && currentWindowUnderPointer != m_previousCaptureWindow)) {
402
403 QPoint wumLocalPos;
404 if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) {
405 wumLocalPos = wumPlatformWindow->mapFromGlobal(globalPos);
406 wumPlatformWindow->applyCursor();
407 }
408 qCDebug(lcQpaEvents) << "Entering window " << currentWindowUnderPointer;
409 QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, wumLocalPos, globalPos);
410 }
411
412 // We need to track m_windowUnderPointer separately from m_currentWindow, as Windows
413 // mouse tracking will not trigger WM_MOUSELEAVE for leaving window when mouse capture is set.
414 m_windowUnderPointer = currentWindowUnderPointer;
415 }
416
417 m_previousCaptureWindow = hasCapture ? window : nullptr;
418}
419
420void QWindowsPointerHandler::handleWindowActivation(QWindow *window,
421 QEvent::Type eventType)
422{
423 if (eventType != QEvent::MouseButtonPress)
424 return;
425
426 auto *focusWindow = QGuiApplication::focusWindow();
427 if (window->isTopLevel()) {
428 // Windows does not send WM_MOUSEACTIVATE to an already-active top-level,
429 // so if focus currently sits in an embedded descendant (e.g. a native
430 // child window hosted via WindowContainer) we have to activate the
431 // top-level ourselves on mouse press.
432 if (focusWindow && window->isAncestorOf(focusWindow))
433 window->requestActivate();
434 } else if (!window->inherits("QWidgetWindow") && focusWindow != window) {
435 // "Click to focus" for native child windows. Skip QWidgetWindow, which
436 // handles focus at the widget level.
437 window->requestActivate();
438 }
439}
440
441bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
442 QtWindows::WindowsEventType et,
443 MSG msg, PVOID vTouchInfo, quint32 count)
444{
445 Q_UNUSED(hwnd);
446
447 auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
448
449 if (et & QtWindows::NonClientEventFlag)
450 return false; // Let DefWindowProc() handle Non Client messages.
451
452 if (count < 1)
453 return false;
454
455 if (msg.message == WM_POINTERCAPTURECHANGED) {
456 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
457 QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(),
458 keyMapper->queryKeyboardModifiers());
459 m_lastTouchPoints.clear();
460 return true;
461 }
462
463 if (msg.message == WM_POINTERLEAVE) {
464 for (quint32 i = 0; i < count; ++i) {
465 const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
466 int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
467 if (id != -1)
468 m_lastTouchPoints.remove(id);
469 }
470 // Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
471 QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
472 }
473
474 // Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
475 if (msg.message > WM_POINTERUP)
476 return false;
477
478 const QScreen *screen = window->screen();
479 if (!screen)
480 screen = QGuiApplication::primaryScreen();
481 if (!screen)
482 return false;
483
484 const QRect screenGeometry = screen->geometry();
485
486 QList<QWindowSystemInterface::TouchPoint> touchPoints;
487
488 if (QWindowsContext::verbose > 1)
489 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
490 << __FUNCTION__
491 << " message=" << Qt::hex << msg.message
492 << " count=" << Qt::dec << count;
493
494 QEventPoint::States allStates;
495 QSet<int> inputIds;
496
497 for (quint32 i = 0; i < count; ++i) {
498 if (QWindowsContext::verbose > 1)
499 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
500 << " TouchPoint id=" << touchInfo[i].pointerInfo.pointerId
501 << " frame=" << touchInfo[i].pointerInfo.frameId
502 << " flags=" << Qt::hex << touchInfo[i].pointerInfo.pointerFlags;
503
504 QWindowSystemInterface::TouchPoint touchPoint;
505 const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
506 int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
507 if (id == -1) {
508 // Start tracking after fingers touch the screen. Ignore bogus updates after touch is released.
509 if ((touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) == 0)
510 continue;
511 id = m_touchInputIDToTouchPointID.size();
512 m_touchInputIDToTouchPointID.insert(pointerId, id);
513 }
514 touchPoint.id = id;
515 touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ?
516 touchInfo[i].pressure / 1024.0 : 1.0;
517 if (m_lastTouchPoints.contains(touchPoint.id))
518 touchPoint.normalPosition = m_lastTouchPoints.value(touchPoint.id).normalPosition;
519
520 const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x,
521 touchInfo[i].pointerInfo.ptPixelLocation.y);
522
523 if (touchInfo[i].touchMask & TOUCH_MASK_CONTACTAREA)
524 touchPoint.area.setSize(QSizeF(touchInfo[i].rcContact.right - touchInfo[i].rcContact.left,
525 touchInfo[i].rcContact.bottom - touchInfo[i].rcContact.top));
526 touchPoint.area.moveCenter(screenPos);
527 QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(),
528 screenPos.y() / screenGeometry.height());
529 const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
530 touchPoint.normalPosition = normalPosition;
531
532 if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
533 touchPoint.state = QEventPoint::State::Pressed;
534 m_lastTouchPoints.insert(touchPoint.id, touchPoint);
535 } else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) {
536 touchPoint.state = QEventPoint::State::Released;
537 m_lastTouchPoints.remove(touchPoint.id);
538 } else {
539 touchPoint.state = stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated;
540 m_lastTouchPoints.insert(touchPoint.id, touchPoint);
541 }
542 allStates |= touchPoint.state;
543
544 touchPoints.append(touchPoint);
545 inputIds.insert(touchPoint.id);
546
547 // Avoid getting repeated messages for this frame if there are multiple pointerIds
548 SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
549 }
550
551 // Some devices send touches for each finger in a different message/frame, instead of consolidating
552 // them in the same frame as we were expecting. We account for missing unreleased touches here.
553 for (auto tp : std::as_const(m_lastTouchPoints)) {
554 if (!inputIds.contains(tp.id)) {
555 tp.state = QEventPoint::State::Stationary;
556 allStates |= tp.state;
557 touchPoints.append(tp);
558 }
559 }
560
561 if (touchPoints.count() == 0)
562 return false;
563
564 // all touch points released, forget the ids we've seen.
565 if (allStates == QEventPoint::State::Released)
566 m_touchInputIDToTouchPointID.clear();
567
568 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
569 QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints,
570 keyMapper->queryKeyboardModifiers());
571 return false; // Allow mouse messages to be generated.
572}
573
574#if QT_CONFIG(tabletevent)
575QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::findTabletDevice(QPointingDevice::PointerType pointerType) const
576{
577 for (const auto &d : m_tabletDevices) {
578 if (d->pointerType() == pointerType)
579 return d;
580 }
581 return {};
582}
583#endif
584
585bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et,
586 MSG msg, PVOID vPenInfo)
587{
588#if QT_CONFIG(tabletevent)
589 if (et & QtWindows::NonClientEventFlag)
590 return false; // Let DefWindowProc() handle Non Client messages.
591
592 auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
593
594 RECT pRect, dRect;
595 if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
596 return false;
597
598 const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
599 const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y);
600 const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos);
601 const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left)
602 / (pRect.right - pRect.left) * (dRect.right - dRect.left),
603 dRect.top + qreal(penInfo->pointerInfo.ptHimetricLocation.y - pRect.top)
604 / (pRect.bottom - pRect.top) * (dRect.bottom - dRect.top));
605 const bool hasPressure = (penInfo->penMask & PEN_MASK_PRESSURE) != 0;
606 const bool hasRotation = (penInfo->penMask & PEN_MASK_ROTATION) != 0;
607 const qreal pressure = hasPressure ? qreal(penInfo->pressure) / 1024.0 : 0.5;
608 const qreal rotation = hasRotation ? qreal(penInfo->rotation) : 0.0;
609 const qreal tangentialPressure = 0.0;
610 const bool hasTiltX = (penInfo->penMask & PEN_MASK_TILT_X) != 0;
611 const bool hasTiltY = (penInfo->penMask & PEN_MASK_TILT_Y) != 0;
612 const int xTilt = hasTiltX ? penInfo->tiltX : 0;
613 const int yTilt = hasTiltY ? penInfo->tiltY : 0;
614 const int z = 0;
615
616 if (QWindowsContext::verbose > 1)
617 qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
618 << __FUNCTION__ << " systemId=" << systemId
619 << " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos
620 << " message=" << Qt::hex << msg.message
621 << " flags=" << Qt::hex << penInfo->pointerInfo.pointerFlags;
622
623 QPointingDevice::PointerType type;
624 // Since it may be the middle button, so if the checks fail then it should
625 // be set to Middle if it was used.
626 Qt::MouseButtons mouseButtons = queryMouseButtons();
627
628 const bool pointerInContact = IS_POINTER_INCONTACT_WPARAM(msg.wParam);
629 if (pointerInContact)
630 mouseButtons = Qt::LeftButton;
631
632 if (penInfo->penFlags & (PEN_FLAG_ERASER | PEN_FLAG_INVERTED)) {
633 type = QPointingDevice::PointerType::Eraser;
634 } else {
635 type = QPointingDevice::PointerType::Pen;
636 if (pointerInContact && penInfo->penFlags & PEN_FLAG_BARREL)
637 mouseButtons = Qt::RightButton; // Either left or right, not both
638 }
639
640 auto device = findTabletDevice(type);
641 if (device.isNull()) {
642 QInputDevice::Capabilities caps(QInputDevice::Capability::Position
643 | QInputDevice::Capability::MouseEmulation
644 | QInputDevice::Capability::Hover);
645 if (hasPressure)
646 caps |= QInputDevice::Capability::Pressure;
647 if (hasRotation)
648 caps |= QInputDevice::Capability::Rotation;
649 if (hasTiltX)
650 caps |= QInputDevice::Capability::XTilt;
651 if (hasTiltY)
652 caps |= QInputDevice::Capability::YTilt;
653 const qint64 uniqueId = systemId | (qint64(type) << 32L);
654 device.reset(new QPointingDevice(QStringLiteral("wmpointer"),
655 systemId, QInputDevice::DeviceType::Stylus,
656 type, caps, 1, 3, QString(),
657 QPointingDeviceUniqueId::fromNumericId(uniqueId)));
658 QWindowSystemInterface::registerInputDevice(device.data());
659 m_tabletDevices.append(device);
660 }
661
662 const auto uniqueId = device->uniqueId().numericId();
663 m_activeTabletDevice = device;
664
665 switch (msg.message) {
666 case WM_POINTERENTER: {
667 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), true);
668 m_windowUnderPointer = window;
669 // The local coordinates may fall outside the window.
670 // Wait until the next update to send the enter event.
671 m_needsEnterOnPointerUpdate = true;
672 break;
673 }
674 case WM_POINTERLEAVE:
675 if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) {
676 QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
677 m_windowUnderPointer = nullptr;
678 m_currentWindow = nullptr;
679 }
680 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), false);
681 break;
682 case WM_POINTERDOWN:
683 case WM_POINTERUP:
684 case WM_POINTERUPDATE: {
685 QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
686 if (!target && m_windowUnderPointer)
687 target = m_windowUnderPointer;
688 if (!target)
689 target = window;
690
691 if (m_needsEnterOnPointerUpdate) {
692 m_needsEnterOnPointerUpdate = false;
693 if (window != m_currentWindow) {
694 // make sure we subscribe to leave events for this window
695 trackLeave(hwnd);
696
697 QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos);
698 m_currentWindow = window;
699 if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target))
700 wumPlatformWindow->applyCursor();
701 }
702 }
703 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
704 const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
705
706 QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(),
707 localPos, hiResGlobalPos, mouseButtons,
708 pressure, xTilt, yTilt, tangentialPressure,
709 rotation, z, keyModifiers);
710 return false; // Allow mouse messages to be generated.
711 }
712 }
713 return true;
714#else
715 Q_UNUSED(window);
716 Q_UNUSED(hwnd);
717 Q_UNUSED(et);
718 Q_UNUSED(msg);
719 Q_UNUSED(vPenInfo);
720 return false;
721#endif
722}
723
725{
726 // For details, see
727 // https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages
728 const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
729 const LONG_PTR MI_WP_SIGNATURE = 0xFF515700;
730
731 return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE);
732}
733
734bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
735 QWindow *currentWindowUnderPointer,
736 MSG msg,
737 QPoint globalPos,
738 Qt::KeyboardModifiers keyModifiers)
739{
740 QWindow *receiver = currentWindowUnderPointer;
741 if (!isValidWheelReceiver(receiver))
742 receiver = window;
743 if (!isValidWheelReceiver(receiver))
744 return true;
745
746 int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam);
747
748 // Qt horizontal wheel rotation orientation is opposite to the one in WM_MOUSEHWHEEL
749 if (msg.message == WM_MOUSEHWHEEL)
750 delta = -delta;
751
752 const QPoint angleDelta = (msg.message == WM_MOUSEHWHEEL || (keyModifiers & Qt::AltModifier)) ?
753 QPoint(delta, 0) : QPoint(0, delta);
754
755 QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
756
757 QWindowSystemInterface::handleWheelEvent(receiver, msg.time, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
758 return true;
759}
760
761// Process legacy mouse messages here.
763 HWND hwnd,
764 QtWindows::WindowsEventType et,
765 MSG msg,
766 LRESULT *result)
767{
768 *result = 0;
769
770 QPoint localPos;
771 QPoint globalPos;
772 QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
773
774 if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
775 globalPos = eventPos;
776 localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
777 } else {
778 if (QWindowsBaseWindow::isRtlLayout(hwnd)) {
779 RECT clientArea;
780 GetClientRect(hwnd, &clientArea);
781 eventPos.setX(clientArea.right - eventPos.x());
782 }
783
784 globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
785 auto targetHwnd = hwnd;
786 if (auto *pw = window->handle())
787 targetHwnd = HWND(pw->winId());
788 localPos = targetHwnd == hwnd
789 ? eventPos
790 : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
791 }
792
793 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
794 const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
795 QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
796
797 if (et == QtWindows::MouseWheelEvent)
798 return translateMouseWheelEvent(window, currentWindowUnderPointer, msg, globalPos, keyModifiers);
799
800 // Windows sends a mouse move with no buttons pressed to signal "Enter"
801 // when a window is shown over the cursor. Discard the event and only use
802 // it for generating QEvent::Enter to be consistent with other platforms -
803 // X11 and macOS.
804 bool discardEvent = false;
805 if (msg.message == WM_MOUSEMOVE) {
806 Q_CONSTINIT static QPoint lastMouseMovePos;
807 if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos))
808 discardEvent = true;
809 lastMouseMovePos = globalPos;
810 }
811
812 Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
813 const QPointingDevice *device = primaryMouse();
814
815 // Following the logic of the old mouse handler, only events synthesized
816 // for touch screen are marked as such. On some systems, using the bit 7 of
817 // the extra msg info for checking if synthesized for touch does not work,
818 // so we use the pointer type of the last pointer message.
820 switch (m_pointerType) {
821 case QT_PT_TOUCH:
822 if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)
823 return false;
824 source = Qt::MouseEventSynthesizedBySystem;
825 if (!m_touchDevice.isNull())
826 device = m_touchDevice.data();
827 break;
828 case QT_PT_PEN:
829#if QT_CONFIG(tabletevent)
830 qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
831 return false;
832#endif
833 break;
834 }
835 }
836
837 const MouseEvent mouseEvent = eventFromMsg(msg);
838 Qt::MouseButtons mouseButtons;
839
840 if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
841 mouseButtons = queryMouseButtons();
842 else
843 mouseButtons = mouseButtonsFromKeyState(msg.wParam);
844
845 // When the left/right mouse buttons are pressed over the window title bar
846 // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP
847 // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE.
848 // We detect it and generate the missing release events here. (QTBUG-75678)
849 // The last event vars are cleared on QWindowsContext::handleExitSizeMove()
850 // to avoid generating duplicated release events.
851 if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
852 && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
853 && (m_lastEventButton & mouseButtons) == 0) {
854 auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
855 QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
856 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
857 releaseType, keyModifiers, source);
858 }
859 m_lastEventType = mouseEvent.type;
860 m_lastEventButton = mouseEvent.button;
861
862 if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
863 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
864 mouseEvent.button, mouseEvent.type, keyModifiers, source);
865 return false; // Allow further event processing
866 }
867
868 if (msg.message == WM_MOUSELEAVE) {
869 if (window == m_currentWindow) {
870 QWindow *leaveTarget = m_windowUnderPointer ? m_windowUnderPointer : m_currentWindow;
871 qCDebug(lcQpaEvents) << "Leaving window " << leaveTarget;
872 QWindowSystemInterface::handleLeaveEvent(leaveTarget);
873 m_windowUnderPointer = nullptr;
874 m_currentWindow = nullptr;
875 }
876 return true;
877 }
878
879 handleWindowActivation(window, mouseEvent.type);
880 handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons);
881 handleEnterLeave(window, currentWindowUnderPointer, globalPos);
882
883 if (!discardEvent && mouseEvent.type != QEvent::None) {
884 QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
885 mouseEvent.button, mouseEvent.type, keyModifiers, source);
886 }
887
888 // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
889 // is sent for unhandled WM_XBUTTONDOWN.
890 return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
891 || QWindowSystemInterface::flushWindowSystemEvents();
892}
893
894QT_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)