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