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