/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #if defined(WINVER) && WINVER < 0x0603 # undef WINVER #endif #if !defined(WINVER) # define WINVER 0x0603 // Enable pointer functions for MinGW #endif #include "qwindowspointerhandler.h" #include "qwindowskeymapper.h" #include "qwindowscontext.h" #include "qwindowswindow.h" #include "qwindowsintegration.h" #include "qwindowsscreen.h" #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE enum { QT_PT_POINTER = 1, QT_PT_TOUCH = 2, QT_PT_PEN = 3, QT_PT_MOUSE = 4, QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD }; bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { *result = 0; const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam); POINTER_INPUT_TYPE pointerType; if (!QWindowsContext::user32dll.getPointerType(pointerId, &pointerType)) { qWarning() << "GetPointerType() failed:" << qt_error_string(); return false; } switch (pointerType) { case QT_PT_POINTER: case QT_PT_MOUSE: case QT_PT_TOUCHPAD: { // Let Mouse/TouchPad be handled using legacy messages. return false; } case QT_PT_TOUCH: { quint32 pointerCount = 0; if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) { qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string(); return false; } QVarLengthArray touchInfo(pointerCount); if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) { qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string(); return false; } if (!pointerCount) return false; // The history count is the same for all the touchpoints in touchInfo quint32 historyCount = touchInfo[0].pointerInfo.historyCount; // dispatch any skipped frames if event compression is disabled by the app if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) { touchInfo.resize(pointerCount * historyCount); if (!QWindowsContext::user32dll.getPointerFrameTouchInfoHistory(pointerId, &historyCount, &pointerCount, touchInfo.data())) { qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string(); return false; } // history frames are returned with the most recent frame first so we iterate backwards bool result = true; for (auto it = touchInfo.rbegin(), end = touchInfo.rend(); it != end; it += pointerCount) { result &= translateTouchEvent(window, hwnd, et, msg, &(*(it + (pointerCount - 1))), pointerCount); } return result; } return translateTouchEvent(window, hwnd, et, msg, touchInfo.data(), pointerCount); } case QT_PT_PEN: { POINTER_PEN_INFO penInfo; if (!QWindowsContext::user32dll.getPointerPenInfo(pointerId, &penInfo)) { qWarning() << "GetPointerPenInfo() failed:" << qt_error_string(); return false; } quint32 historyCount = penInfo.pointerInfo.historyCount; // dispatch any skipped frames if generic or tablet event compression is disabled by the app if (historyCount > 1 && (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents) || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) { QVarLengthArray penInfoHistory(historyCount); if (!QWindowsContext::user32dll.getPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) { qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string(); return false; } // history frames are returned with the most recent frame first so we iterate backwards bool result = true; for (auto it = penInfoHistory.rbegin(), end = penInfoHistory.rend(); it != end; ++it) { result &= translatePenEvent(window, hwnd, et, msg, &(*(it))); } return result; } return translatePenEvent(window, hwnd, et, msg, &penInfo); } } return false; } namespace { struct MouseEvent { QEvent::Type type; Qt::MouseButton button; }; } // namespace static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON... { return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton; } static inline MouseEvent eventFromMsg(const MSG &msg) { switch (msg.message) { case WM_MOUSEMOVE: return {QEvent::MouseMove, Qt::NoButton}; case WM_LBUTTONDOWN: return {QEvent::MouseButtonPress, Qt::LeftButton}; case WM_LBUTTONUP: return {QEvent::MouseButtonRelease, Qt::LeftButton}; case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press return {QEvent::MouseButtonPress, Qt::LeftButton}; case WM_MBUTTONDOWN: return {QEvent::MouseButtonPress, Qt::MidButton}; case WM_MBUTTONUP: return {QEvent::MouseButtonRelease, Qt::MidButton}; case WM_MBUTTONDBLCLK: return {QEvent::MouseButtonPress, Qt::MidButton}; case WM_RBUTTONDOWN: return {QEvent::MouseButtonPress, Qt::RightButton}; case WM_RBUTTONUP: return {QEvent::MouseButtonRelease, Qt::RightButton}; case WM_RBUTTONDBLCLK: return {QEvent::MouseButtonPress, Qt::RightButton}; case WM_XBUTTONDOWN: return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; case WM_XBUTTONUP: return {QEvent::MouseButtonRelease, extraButton(msg.wParam)}; case WM_XBUTTONDBLCLK: return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; case WM_NCMOUSEMOVE: return {QEvent::NonClientAreaMouseMove, Qt::NoButton}; case WM_NCLBUTTONDOWN: return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton}; case WM_NCLBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton}; case WM_NCLBUTTONDBLCLK: return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton}; case WM_NCMBUTTONDOWN: return {QEvent::NonClientAreaMouseButtonPress, Qt::MidButton}; case WM_NCMBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::MidButton}; case WM_NCMBUTTONDBLCLK: return {QEvent::NonClientAreaMouseButtonPress, Qt::MidButton}; case WM_NCRBUTTONDOWN: return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; case WM_NCRBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton}; case WM_NCRBUTTONDBLCLK: return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; default: // WM_MOUSELEAVE break; } return {QEvent::None, Qt::NoButton}; } static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState) { Qt::MouseButtons result = Qt::NoButton; if (keyState & MK_LBUTTON) result |= Qt::LeftButton; if (keyState & MK_RBUTTON) result |= Qt::RightButton; if (keyState & MK_MBUTTON) result |= Qt::MiddleButton; if (keyState & MK_XBUTTON1) result |= Qt::XButton1; if (keyState & MK_XBUTTON2) result |= Qt::XButton2; return result; } static Qt::MouseButtons queryMouseButtons() { Qt::MouseButtons result = Qt::NoButton; const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON); if (GetAsyncKeyState(VK_LBUTTON) < 0) result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton; if (GetAsyncKeyState(VK_RBUTTON) < 0) result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton; if (GetAsyncKeyState(VK_MBUTTON) < 0) result |= Qt::MidButton; if (GetAsyncKeyState(VK_XBUTTON1) < 0) result |= Qt::XButton1; if (GetAsyncKeyState(VK_XBUTTON2) < 0) result |= Qt::XButton2; return result; } static QWindow *getWindowUnderPointer(QWindow *window, QPoint globalPos) { QWindow *currentWindowUnderPointer = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT); while (currentWindowUnderPointer && currentWindowUnderPointer->flags() & Qt::WindowTransparentForInput) currentWindowUnderPointer = currentWindowUnderPointer->parent(); // QTBUG-44332: When Qt is running at low integrity level and // a Qt Window is parented on a Window of a higher integrity process // using QWindow::fromWinId() (for example, Qt running in a browser plugin) // ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED) if (!currentWindowUnderPointer) { const QRect clientRect(QPoint(0, 0), window->size()); if (clientRect.contains(globalPos)) currentWindowUnderPointer = window; } return currentWindowUnderPointer; } static bool trackLeave(HWND hwnd) { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; tme.dwHoverTime = HOVER_DEFAULT; return TrackMouseEvent(&tme); } static bool isValidWheelReceiver(QWindow *candidate) { if (candidate) { const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate); if (toplevel->handle() && toplevel->handle()->isForeignWindow()) return true; if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel)) return !ww->testFlag(QWindowsWindow::BlockedByModal); } return false; } static QTouchDevice *createTouchDevice() { const int digitizers = GetSystemMetrics(SM_DIGITIZER); if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH))) return nullptr; const int tabletPc = GetSystemMetrics(SM_TABLETPC); const int maxTouchPoints = GetSystemMetrics(SM_MAXIMUMTOUCHES); qCDebug(lcQpaEvents) << "Digitizers:" << hex << showbase << (digitizers & ~NID_READY) << "Ready:" << (digitizers & NID_READY) << dec << noshowbase << "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints; QTouchDevice *result = new QTouchDevice; result->setType(digitizers & NID_INTEGRATED_TOUCH ? QTouchDevice::TouchScreen : QTouchDevice::TouchPad); QTouchDevice::Capabilities capabilities = QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition; if (result->type() == QTouchDevice::TouchPad) capabilities |= QTouchDevice::MouseEmulation; result->setCapabilities(capabilities); result->setMaximumTouchPoints(maxTouchPoints); return result; } QTouchDevice *QWindowsPointerHandler::ensureTouchDevice() { if (!m_touchDevice) m_touchDevice = createTouchDevice(); return m_touchDevice; } void QWindowsPointerHandler::handleCaptureRelease(QWindow *window, QWindow *currentWindowUnderPointer, HWND hwnd, QEvent::Type eventType, Qt::MouseButtons mouseButtons) { QWindowsWindow *platformWindow = static_cast(window->handle()); // Qt expects the platform plugin to capture the mouse on any button press until release. if (!platformWindow->hasMouseCapture() && eventType == QEvent::MouseButtonPress) { platformWindow->setMouseGrabEnabled(true); platformWindow->setFlag(QWindowsWindow::AutoMouseCapture); qCDebug(lcQpaEvents) << "Automatic mouse capture " << window; // Implement "Click to focus" for native child windows (unless it is a native widget window). if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window) window->requestActivate(); } else if (platformWindow->hasMouseCapture() && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture) && eventType == QEvent::MouseButtonRelease && !mouseButtons) { platformWindow->setMouseGrabEnabled(false); qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window; } // Enter new window: track to generate leave event. // If there is an active capture, only track if the current window is capturing, // so we don't get extra leave when cursor leaves the application. if (window != m_currentWindow && (!platformWindow->hasMouseCapture() || currentWindowUnderPointer == window)) { trackLeave(hwnd); m_currentWindow = window; } } void QWindowsPointerHandler::handleEnterLeave(QWindow *window, QWindow *currentWindowUnderPointer, QPoint globalPos) { QWindowsWindow *platformWindow = static_cast(window->handle()); const bool hasCapture = platformWindow->hasMouseCapture(); // No enter or leave events are sent as long as there is an autocapturing window. if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) { // Leave is needed if: // 1) There is no capture and we move from a window to another window. // Note: Leaving the application entirely is handled in translateMouseEvent(WM_MOUSELEAVE). // 2) There is capture and we move out of the capturing window. // 3) There is a new capture and we were over another window. if ((m_windowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer && (!hasCapture || window == m_windowUnderPointer)) || (hasCapture && m_previousCaptureWindow != window && m_windowUnderPointer && m_windowUnderPointer != window)) { qCDebug(lcQpaEvents) << "Leaving window " << m_windowUnderPointer; QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer); if (hasCapture && currentWindowUnderPointer != window) { // Clear tracking if capturing and current window is not the capturing window // to avoid leave when mouse actually leaves the application. m_currentWindow = nullptr; // We are not officially in any window, but we need to set some cursor to clear // whatever cursor the left window had, so apply the cursor of the capture window. platformWindow->applyCursor(); } } // Enter is needed if: // 1) There is no capture and we move to a new window. // 2) There is capture and we move into the capturing window. // 3) The capture just ended and we are over non-capturing window. if ((currentWindowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer && (!hasCapture || currentWindowUnderPointer == window)) || (m_previousCaptureWindow && !hasCapture && currentWindowUnderPointer && currentWindowUnderPointer != m_previousCaptureWindow)) { QPoint wumLocalPos; if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) { wumLocalPos = wumPlatformWindow->mapFromGlobal(globalPos); wumPlatformWindow->applyCursor(); } qCDebug(lcQpaEvents) << "Entering window " << currentWindowUnderPointer; QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, wumLocalPos, globalPos); } // We need to track m_windowUnderPointer separately from m_currentWindow, as Windows // mouse tracking will not trigger WM_MOUSELEAVE for leaving window when mouse capture is set. m_windowUnderPointer = currentWindowUnderPointer; } m_previousCaptureWindow = hasCapture ? window : nullptr; } bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vTouchInfo, quint32 count) { Q_UNUSED(hwnd); if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. if (count < 1) return false; if (msg.message == WM_POINTERCAPTURECHANGED) { QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice, QWindowsKeyMapper::queryKeyboardModifiers()); m_lastTouchPositions.clear(); return true; } // Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc. if (msg.message > WM_POINTERUP) return false; const QScreen *screen = window->screen(); if (!screen) screen = QGuiApplication::primaryScreen(); if (!screen) return false; POINTER_TOUCH_INFO *touchInfo = static_cast(vTouchInfo); const QRect screenGeometry = screen->geometry(); QList touchPoints; if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents).noquote().nospace() << showbase << __FUNCTION__ << " message=" << hex << msg.message << " count=" << dec << count; Qt::TouchPointStates allStates = 0; for (quint32 i = 0; i < count; ++i) { if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents).noquote().nospace() << showbase << " TouchPoint id=" << touchInfo[i].pointerInfo.pointerId << " frame=" << touchInfo[i].pointerInfo.frameId << " flags=" << hex << touchInfo[i].pointerInfo.pointerFlags; QWindowSystemInterface::TouchPoint touchPoint; const quint32 pointerId = touchInfo[i].pointerInfo.pointerId; int id = m_touchInputIDToTouchPointID.value(pointerId, -1); if (id == -1) { id = m_touchInputIDToTouchPointID.size(); m_touchInputIDToTouchPointID.insert(pointerId, id); } touchPoint.id = id; touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ? touchInfo[i].pressure / 1024.0 : 1.0; if (m_lastTouchPositions.contains(touchPoint.id)) touchPoint.normalPosition = m_lastTouchPositions.value(touchPoint.id); const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x, touchInfo[i].pointerInfo.ptPixelLocation.y); if (touchInfo[i].touchMask & TOUCH_MASK_CONTACTAREA) touchPoint.area.setSize(QSizeF(touchInfo[i].rcContact.right - touchInfo[i].rcContact.left, touchInfo[i].rcContact.bottom - touchInfo[i].rcContact.top)); touchPoint.area.moveCenter(screenPos); QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(), screenPos.y() / screenGeometry.height()); const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition); touchPoint.normalPosition = normalPosition; if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) { touchPoint.state = Qt::TouchPointPressed; m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition); } else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) { touchPoint.state = Qt::TouchPointReleased; m_lastTouchPositions.remove(touchPoint.id); } else { touchPoint.state = stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved; m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition); } allStates |= touchPoint.state; touchPoints.append(touchPoint); // Avoid getting repeated messages for this frame if there are multiple pointerIds QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); } // all touch points released, forget the ids we've seen. if (allStates == Qt::TouchPointReleased) m_touchInputIDToTouchPointID.clear(); QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, QWindowsKeyMapper::queryKeyboardModifiers()); return false; // Allow mouse messages to be generated. } bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPenInfo) { #if QT_CONFIG(tabletevent) if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. POINTER_PEN_INFO *penInfo = static_cast(vPenInfo); RECT pRect, dRect; if (!QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect)) return false; const qint64 sourceDevice = (qint64)penInfo->pointerInfo.sourceDevice; const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y); const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos); const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left) / (pRect.right - pRect.left) * (dRect.right - dRect.left), dRect.top + qreal(penInfo->pointerInfo.ptHimetricLocation.y - pRect.top) / (pRect.bottom - pRect.top) * (dRect.bottom - dRect.top)); const qreal pressure = (penInfo->penMask & PEN_MASK_PRESSURE) ? qreal(penInfo->pressure) / 1024.0 : 0.5; const qreal rotation = (penInfo->penMask & PEN_MASK_ROTATION) ? qreal(penInfo->rotation) : 0.0; const qreal tangentialPressure = 0.0; const int xTilt = (penInfo->penMask & PEN_MASK_TILT_X) ? penInfo->tiltX : 0; const int yTilt = (penInfo->penMask & PEN_MASK_TILT_Y) ? penInfo->tiltY : 0; const int z = 0; if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents).noquote().nospace() << showbase << __FUNCTION__ << " sourceDevice=" << sourceDevice << " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos << " message=" << hex << msg.message << " flags=" << hex << penInfo->pointerInfo.pointerFlags; const QTabletEvent::TabletDevice device = QTabletEvent::Stylus; QTabletEvent::PointerType type; Qt::MouseButtons mouseButtons; const bool pointerInContact = IS_POINTER_INCONTACT_WPARAM(msg.wParam); if (pointerInContact) mouseButtons = Qt::LeftButton; if (penInfo->penFlags & (PEN_FLAG_ERASER | PEN_FLAG_INVERTED)) { type = QTabletEvent::Eraser; } else { type = QTabletEvent::Pen; if (pointerInContact && penInfo->penFlags & PEN_FLAG_BARREL) mouseButtons = Qt::RightButton; // Either left or right, not both } switch (msg.message) { case WM_POINTERENTER: { QWindowSystemInterface::handleTabletEnterProximityEvent(device, type, sourceDevice); m_windowUnderPointer = window; // The local coordinates may fall outside the window. // Wait until the next update to send the enter event. m_needsEnterOnPointerUpdate = true; break; } case WM_POINTERLEAVE: if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) { QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer); m_windowUnderPointer = nullptr; m_currentWindow = nullptr; } QWindowSystemInterface::handleTabletLeaveProximityEvent(device, type, sourceDevice); break; case WM_POINTERDOWN: case WM_POINTERUP: case WM_POINTERUPDATE: { QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).target; // Pass to window that grabbed it. if (!target && m_windowUnderPointer) target = m_windowUnderPointer; if (!target) target = window; if (m_needsEnterOnPointerUpdate) { m_needsEnterOnPointerUpdate = false; if (window != m_currentWindow) { QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos); m_currentWindow = window; if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target)) wumPlatformWindow->applyCursor(); } } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, sourceDevice, keyModifiers); return false; // Allow mouse messages to be generated. } } return true; #else Q_UNUSED(window); Q_UNUSED(hwnd); Q_UNUSED(et); Q_UNUSED(msg); Q_UNUSED(vPenInfo); return false; #endif } static inline bool isMouseEventSynthesizedFromPenOrTouch() { // For details, see // https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00; const LONG_PTR MI_WP_SIGNATURE = 0xFF515700; return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE); } bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window, QWindow *currentWindowUnderPointer, MSG msg, QPoint globalPos, Qt::KeyboardModifiers keyModifiers) { QWindow *receiver = currentWindowUnderPointer; if (!isValidWheelReceiver(receiver)) receiver = window; if (!isValidWheelReceiver(receiver)) return true; int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam); // Qt horizontal wheel rotation orientation is opposite to the one in WM_MOUSEHWHEEL if (msg.message == WM_MOUSEHWHEEL) delta = -delta; const QPoint angleDelta = (msg.message == WM_MOUSEHWHEEL || (keyModifiers & Qt::AltModifier)) ? QPoint(delta, 0) : QPoint(0, delta); QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos); QWindowSystemInterface::handleWheelEvent(window, localPos, globalPos, QPoint(), angleDelta, keyModifiers); return true; } // Process legacy mouse messages here. bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { *result = 0; const QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); QPoint localPos; QPoint globalPos; if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) { globalPos = eventPos; localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos); } else { localPos = eventPos; globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos); } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos); if (et == QtWindows::MouseWheelEvent) return translateMouseWheelEvent(window, currentWindowUnderPointer, msg, globalPos, keyModifiers); // Windows sends a mouse move with no buttons pressed to signal "Enter" // when a window is shown over the cursor. Discard the event and only use // it for generating QEvent::Enter to be consistent with other platforms - // X11 and macOS. bool discardEvent = false; if (msg.message == WM_MOUSEMOVE) { static QPoint lastMouseMovePos; if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos)) discardEvent = true; lastMouseMovePos = globalPos; } Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; if (isMouseEventSynthesizedFromPenOrTouch()) { if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) return false; source = Qt::MouseEventSynthesizedBySystem; } const MouseEvent mouseEvent = eventFromMsg(msg); if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) { const Qt::MouseButtons nonclientButtons = queryMouseButtons(); QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, nonclientButtons, mouseEvent.button, mouseEvent.type, keyModifiers, source); return false; // Allow further event processing } if (msg.message == WM_MOUSELEAVE) { if (window == m_currentWindow) { QWindow *leaveTarget = m_windowUnderPointer ? m_windowUnderPointer : m_currentWindow; qCDebug(lcQpaEvents) << "Leaving window " << leaveTarget; QWindowSystemInterface::handleLeaveEvent(leaveTarget); m_windowUnderPointer = nullptr; m_currentWindow = nullptr; } return true; } const Qt::MouseButtons mouseButtons = mouseButtonsFromKeyState(msg.wParam); handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons); handleEnterLeave(window, currentWindowUnderPointer, globalPos); if (!discardEvent && mouseEvent.type != QEvent::None) { QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, mouseEvent.button, mouseEvent.type, keyModifiers, source); } // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND // is sent for unhandled WM_XBUTTONDOWN. return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK) || QWindowSystemInterface::flushWindowSystemEvents(); } QT_END_NAMESPACE