| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwindowsmousehandler.h" |
| #include "qwindowskeymapper.h" |
| #include "qwindowscontext.h" |
| #include "qwindowswindow.h" |
| #include "qwindowsintegration.h" |
| #include "qwindowsscreen.h" |
| |
| #include <qpa/qwindowsysteminterface.h> |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qscreen.h> |
| #include <QtGui/qtouchdevice.h> |
| #include <QtGui/qwindow.h> |
| #include <QtGui/qcursor.h> |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qscopedpointer.h> |
| |
| #include <windowsx.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| static inline void compressMouseMove(MSG *msg) |
| { |
| // Compress mouse move events |
| if (msg->message == WM_MOUSEMOVE) { |
| MSG mouseMsg; |
| while (PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEFIRST, |
| WM_MOUSELAST, PM_NOREMOVE)) { |
| if (mouseMsg.message == WM_MOUSEMOVE) { |
| #define PEEKMESSAGE_IS_BROKEN 1 |
| #ifdef PEEKMESSAGE_IS_BROKEN |
| // Since the Windows PeekMessage() function doesn't |
| // correctly return the wParam for WM_MOUSEMOVE events |
| // if there is a key release event in the queue |
| // _before_ the mouse event, we have to also consider |
| // key release events (kls 2003-05-13): |
| MSG keyMsg; |
| bool done = false; |
| while (PeekMessage(&keyMsg, nullptr, WM_KEYFIRST, WM_KEYLAST, |
| PM_NOREMOVE)) { |
| if (keyMsg.time < mouseMsg.time) { |
| if ((keyMsg.lParam & 0xC0000000) == 0x40000000) { |
| PeekMessage(&keyMsg, nullptr, keyMsg.message, |
| keyMsg.message, PM_REMOVE); |
| } else { |
| done = true; |
| break; |
| } |
| } else { |
| break; // no key event before the WM_MOUSEMOVE event |
| } |
| } |
| if (done) |
| break; |
| #else |
| // Actually the following 'if' should work instead of |
| // the above key event checking, but apparently |
| // PeekMessage() is broken :-( |
| if (mouseMsg.wParam != msg.wParam) |
| break; // leave the message in the queue because |
| // the key state has changed |
| #endif |
| // Update the passed in MSG structure with the |
| // most recent one. |
| msg->lParam = mouseMsg.lParam; |
| msg->wParam = mouseMsg.wParam; |
| // Extract the x,y coordinates from the lParam as we do in the WndProc |
| msg->pt.x = GET_X_LPARAM(mouseMsg.lParam); |
| msg->pt.y = GET_Y_LPARAM(mouseMsg.lParam); |
| clientToScreen(msg->hwnd, &(msg->pt)); |
| // Remove the mouse move message |
| PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEMOVE, |
| WM_MOUSEMOVE, PM_REMOVE); |
| } else { |
| break; // there was no more WM_MOUSEMOVE event |
| } |
| } |
| } |
| } |
| |
| static inline 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:" << Qt::hex << Qt::showbase << (digitizers & ~NID_READY) |
| << "Ready:" << (digitizers & NID_READY) << Qt::dec << Qt::noshowbase |
| << "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints; |
| auto *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; |
| } |
| |
| /*! |
| \class QWindowsMouseHandler |
| \brief Windows mouse handler |
| |
| Dispatches mouse and touch events. Separate for code cleanliness. |
| |
| \internal |
| \ingroup qt-lighthouse-win |
| */ |
| |
| QWindowsMouseHandler::QWindowsMouseHandler() = default; |
| |
| QTouchDevice *QWindowsMouseHandler::ensureTouchDevice() |
| { |
| if (!m_touchDevice) |
| m_touchDevice = createTouchDevice(); |
| return m_touchDevice; |
| } |
| |
| void QWindowsMouseHandler::clearEvents() |
| { |
| m_lastEventType = QEvent::None; |
| m_lastEventButton = Qt::NoButton; |
| } |
| |
| Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons() |
| { |
| Qt::MouseButtons result = nullptr; |
| 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 QPoint lastMouseMovePos; |
| |
| namespace { |
| struct MouseEvent { |
| QEvent::Type type; |
| Qt::MouseButton button; |
| }; |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| QDebug operator<<(QDebug d, const MouseEvent &e) |
| { |
| QDebugStateSaver saver(d); |
| d.nospace(); |
| d << "MouseEvent(" << e.type << ", " << e.button << ')'; |
| return d; |
| } |
| #endif // QT_NO_DEBUG_STREAM |
| } // 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}; |
| } |
| |
| bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, |
| QtWindows::WindowsEventType et, |
| MSG msg, LRESULT *result) |
| { |
| enum : quint64 { signatureMask = 0xffffff00, miWpSignature = 0xff515700 }; |
| |
| if (et == QtWindows::MouseWheelEvent) |
| return translateMouseWheelEvent(window, hwnd, msg, result); |
| |
| QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); |
| if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) { |
| RECT clientArea; |
| GetClientRect(hwnd, &clientArea); |
| winEventPosition.setX(clientArea.right - winEventPosition.x()); |
| } |
| |
| QPoint clientPosition; |
| QPoint globalPosition; |
| if (et & QtWindows::NonClientEventFlag) { |
| globalPosition = winEventPosition; |
| clientPosition = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPosition); |
| } else { |
| clientPosition = winEventPosition; |
| globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition); |
| } |
| |
| // 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) { |
| const bool samePosition = globalPosition == lastMouseMovePos; |
| lastMouseMovePos = globalPosition; |
| if (msg.wParam == 0 && (m_windowUnderMouse.isNull() || samePosition)) |
| discardEvent = true; |
| } |
| |
| Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; |
| |
| // Check for events synthesized from touch. Lower byte is touch index, 0 means pen. |
| static const bool passSynthesizedMouseEvents = |
| !(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch); |
| // Check for events synthesized from touch. Lower 7 bits are touch/pen index, bit 8 indicates touch. |
| // However, when tablet support is active, extraInfo is a packet serial number. This is not a problem |
| // since we do not want to ignore mouse events coming from a tablet. |
| // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320.aspx |
| const auto extraInfo = quint64(GetMessageExtraInfo()); |
| if ((extraInfo & signatureMask) == miWpSignature) { |
| if (extraInfo & 0x80) { // Bit 7 indicates touch event, else tablet pen. |
| source = Qt::MouseEventSynthesizedBySystem; |
| if (!passSynthesizedMouseEvents) |
| return false; |
| } |
| } |
| |
| const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); |
| const MouseEvent mouseEvent = eventFromMsg(msg); |
| Qt::MouseButtons buttons; |
| |
| if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) |
| buttons = queryMouseButtons(); |
| else |
| buttons = keyStateToMouseButtons(msg.wParam); |
| |
| // When the left/right mouse buttons are pressed over the window title bar |
| // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP |
| // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE. |
| // We detect it and generate the missing release events here. (QTBUG-75678) |
| // The last event vars are cleared on QWindowsContext::handleExitSizeMove() |
| // to avoid generating duplicated release events. |
| if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress |
| && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove) |
| && (m_lastEventButton & buttons) == 0) { |
| if (mouseEvent.type == QEvent::NonClientAreaMouseMove) { |
| QWindowSystemInterface::handleFrameStrutMouseEvent(window, clientPosition, globalPosition, buttons, m_lastEventButton, |
| QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source); |
| } else { |
| QWindowSystemInterface::handleMouseEvent(window, clientPosition, globalPosition, buttons, m_lastEventButton, |
| QEvent::MouseButtonRelease, keyModifiers, source); |
| } |
| } |
| m_lastEventType = mouseEvent.type; |
| m_lastEventButton = mouseEvent.button; |
| |
| if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) { |
| QWindowSystemInterface::handleFrameStrutMouseEvent(window, clientPosition, |
| globalPosition, buttons, |
| mouseEvent.button, mouseEvent.type, |
| keyModifiers, source); |
| return false; // Allow further event processing (dragging of windows). |
| } |
| |
| *result = 0; |
| if (msg.message == WM_MOUSELEAVE) { |
| qCDebug(lcQpaEvents) << mouseEvent << "for" << window << "previous window under mouse=" |
| << m_windowUnderMouse << "tracked window=" << m_trackedWindow; |
| |
| // When moving out of a window, WM_MOUSEMOVE within the moved-to window is received first, |
| // so if m_trackedWindow is not the window here, it means the cursor has left the |
| // application. |
| if (window == m_trackedWindow) { |
| QWindow *leaveTarget = m_windowUnderMouse ? m_windowUnderMouse : m_trackedWindow; |
| qCDebug(lcQpaEvents) << "Generating leave event for " << leaveTarget; |
| QWindowSystemInterface::handleLeaveEvent(leaveTarget); |
| m_trackedWindow = nullptr; |
| m_windowUnderMouse = nullptr; |
| } |
| return true; |
| } |
| |
| auto *platformWindow = static_cast<QWindowsWindow *>(window->handle()); |
| |
| // If the window was recently resized via mouse doubleclick on the frame or title bar, |
| // we don't get WM_LBUTTONDOWN or WM_LBUTTONDBLCLK for the second click, |
| // but we will get at least one WM_MOUSEMOVE with left button down and the WM_LBUTTONUP, |
| // which will result undesired mouse press and release events. |
| // To avoid those, we ignore any events with left button down if we didn't |
| // get the original WM_LBUTTONDOWN/WM_LBUTTONDBLCLK. |
| if (msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONDBLCLK) { |
| m_leftButtonDown = true; |
| } else { |
| const bool actualLeftDown = buttons & Qt::LeftButton; |
| if (!m_leftButtonDown && actualLeftDown) { |
| // Autocapture the mouse for current window to and ignore further events until release. |
| // Capture is necessary so we don't get WM_MOUSELEAVEs to confuse matters. |
| // This autocapture is released normally when button is released. |
| if (!platformWindow->hasMouseCapture()) { |
| platformWindow->applyCursor(); |
| platformWindow->setMouseGrabEnabled(true); |
| platformWindow->setFlag(QWindowsWindow::AutoMouseCapture); |
| qCDebug(lcQpaEvents) << "Automatic mouse capture for missing buttondown event" << window; |
| } |
| m_previousCaptureWindow = window; |
| return true; |
| } |
| if (m_leftButtonDown && !actualLeftDown) |
| m_leftButtonDown = false; |
| } |
| |
| // In this context, neither an invisible nor a transparent window (transparent regarding mouse |
| // events, "click-through") can be considered as the window under mouse. |
| QWindow *currentWindowUnderMouse = platformWindow->hasMouseCapture() ? |
| QWindowsScreen::windowAt(globalPosition, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window; |
| while (currentWindowUnderMouse && currentWindowUnderMouse->flags() & Qt::WindowTransparentForInput) |
| currentWindowUnderMouse = currentWindowUnderMouse->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 (!currentWindowUnderMouse) { |
| const QRect clientRect(QPoint(0, 0), window->size()); |
| if (clientRect.contains(winEventPosition)) |
| currentWindowUnderMouse = window; |
| } |
| |
| compressMouseMove(&msg); |
| // Qt expects the platform plugin to capture the mouse on |
| // any button press until release. |
| if (!platformWindow->hasMouseCapture() |
| && (mouseEvent.type == QEvent::MouseButtonPress || mouseEvent.type == QEvent::MouseButtonDblClick)) { |
| 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) |
| && mouseEvent.type == QEvent::MouseButtonRelease |
| && !buttons) { |
| platformWindow->setMouseGrabEnabled(false); |
| qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window; |
| } |
| |
| const bool hasCapture = platformWindow->hasMouseCapture(); |
| const bool currentNotCapturing = hasCapture && currentWindowUnderMouse != 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_trackedWindow && !currentNotCapturing) { |
| TRACKMOUSEEVENT tme; |
| tme.cbSize = sizeof(TRACKMOUSEEVENT); |
| tme.dwFlags = TME_LEAVE; |
| tme.hwndTrack = hwnd; |
| tme.dwHoverTime = HOVER_DEFAULT; // |
| if (!TrackMouseEvent(&tme)) |
| qWarning("TrackMouseEvent failed."); |
| m_trackedWindow = window; |
| } |
| |
| // 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 WM_MOUSELEAVE case. |
| // 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_windowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse |
| && (!hasCapture || window == m_windowUnderMouse)) |
| || (hasCapture && m_previousCaptureWindow != window && m_windowUnderMouse |
| && m_windowUnderMouse != window)) { |
| qCDebug(lcQpaEvents) << "Synthetic leave for " << m_windowUnderMouse; |
| QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse); |
| if (currentNotCapturing) { |
| // Clear tracking if capturing and current window is not the capturing window |
| // to avoid leave when mouse actually leaves the application. |
| m_trackedWindow = 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 ((currentWindowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse |
| && (!hasCapture || currentWindowUnderMouse == window)) |
| || (m_previousCaptureWindow && window != m_previousCaptureWindow && currentWindowUnderMouse |
| && currentWindowUnderMouse != m_previousCaptureWindow)) { |
| QPoint localPosition; |
| qCDebug(lcQpaEvents) << "Entering " << currentWindowUnderMouse; |
| if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderMouse)) { |
| localPosition = wumPlatformWindow->mapFromGlobal(globalPosition); |
| wumPlatformWindow->applyCursor(); |
| } |
| QWindowSystemInterface::handleEnterEvent(currentWindowUnderMouse, localPosition, globalPosition); |
| } |
| // We need to track m_windowUnderMouse separately from m_trackedWindow, as |
| // Windows mouse tracking will not trigger WM_MOUSELEAVE for leaving window when |
| // mouse capture is set. |
| m_windowUnderMouse = currentWindowUnderMouse; |
| } |
| |
| if (!discardEvent && mouseEvent.type != QEvent::None) { |
| QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition, buttons, |
| mouseEvent.button, mouseEvent.type, |
| keyModifiers, source); |
| } |
| m_previousCaptureWindow = hasCapture ? window : nullptr; |
| // 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(); |
| } |
| |
| 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 void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int delta, |
| Qt::Orientation orientation, Qt::KeyboardModifiers mods) |
| { |
| // Redirect wheel event to one of the following, in order of preference: |
| // 1) The window under mouse |
| // 2) The window receiving the event |
| // If a window is blocked by modality, it can't get the event. |
| |
| QWindow *receiver = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE); |
| while (receiver && receiver->flags().testFlag(Qt::WindowTransparentForInput)) |
| receiver = receiver->parent(); |
| bool handleEvent = true; |
| if (!isValidWheelReceiver(receiver)) { |
| receiver = window; |
| if (!isValidWheelReceiver(receiver)) |
| handleEvent = false; |
| } |
| |
| if (handleEvent) { |
| const QPoint point = (orientation == Qt::Vertical) ? QPoint(0, delta) : QPoint(delta, 0); |
| QWindowSystemInterface::handleWheelEvent(receiver, |
| QWindowsGeometryHint::mapFromGlobal(receiver, globalPos), |
| globalPos, QPoint(), point, mods); |
| } |
| } |
| |
| bool QWindowsMouseHandler::translateMouseWheelEvent(QWindow *window, HWND, |
| MSG msg, LRESULT *) |
| { |
| const Qt::KeyboardModifiers mods = keyStateToModifiers(int(msg.wParam)); |
| |
| int delta; |
| if (msg.message == WM_MOUSEWHEEL || msg.message == WM_MOUSEHWHEEL) |
| delta = GET_WHEEL_DELTA_WPARAM(msg.wParam); |
| else |
| delta = int(msg.wParam); |
| |
| Qt::Orientation orientation = (msg.message == WM_MOUSEHWHEEL |
| || (mods & Qt::AltModifier)) ? |
| Qt::Horizontal : Qt::Vertical; |
| |
| // according to the MSDN documentation on WM_MOUSEHWHEEL: |
| // a positive value indicates that the wheel was rotated to the right; |
| // a negative value indicates that the wheel was rotated to the left. |
| // Qt defines this value as the exact opposite, so we have to flip the value! |
| if (msg.message == WM_MOUSEHWHEEL) |
| delta = -delta; |
| |
| const QPoint globalPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); |
| redirectWheelEvent(window, globalPos, delta, orientation, mods); |
| |
| return true; |
| } |
| |
| bool QWindowsMouseHandler::translateScrollEvent(QWindow *window, HWND, |
| MSG msg, LRESULT *) |
| { |
| // This is a workaround against some touchpads that send WM_HSCROLL instead of WM_MOUSEHWHEEL. |
| // We could also handle vertical scroll here but there's no reason to, there's no bug for vertical |
| // (broken vertical scroll would have been noticed long time ago), so lets keep the change small |
| // and minimize the chance for regressions. |
| |
| int delta = 0; |
| switch (LOWORD(msg.wParam)) { |
| case SB_LINELEFT: |
| delta = 120; |
| break; |
| case SB_LINERIGHT: |
| delta = -120; |
| break; |
| case SB_PAGELEFT: |
| delta = 240; |
| break; |
| case SB_PAGERIGHT: |
| delta = -240; |
| break; |
| default: |
| return false; |
| } |
| |
| redirectWheelEvent(window, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier); |
| |
| return true; |
| } |
| |
| // from bool QApplicationPrivate::translateTouchEvent() |
| bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, |
| QtWindows::WindowsEventType, |
| MSG msg, LRESULT *) |
| { |
| using QTouchPoint = QWindowSystemInterface::TouchPoint; |
| using QTouchPointList = QList<QWindowSystemInterface::TouchPoint>; |
| |
| if (!QWindowsContext::instance()->initTouch()) { |
| qWarning("Unable to initialize touch handling."); |
| return true; |
| } |
| |
| const QScreen *screen = window->screen(); |
| if (!screen) |
| screen = QGuiApplication::primaryScreen(); |
| if (!screen) |
| return true; |
| const QRect screenGeometry = screen->geometry(); |
| |
| const int winTouchPointCount = int(msg.wParam); |
| QScopedArrayPointer<TOUCHINPUT> winTouchInputs(new TOUCHINPUT[winTouchPointCount]); |
| memset(winTouchInputs.data(), 0, sizeof(TOUCHINPUT) * size_t(winTouchPointCount)); |
| |
| QTouchPointList touchPoints; |
| touchPoints.reserve(winTouchPointCount); |
| Qt::TouchPointStates allStates = nullptr; |
| |
| GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(msg.lParam), |
| UINT(msg.wParam), winTouchInputs.data(), sizeof(TOUCHINPUT)); |
| for (int i = 0; i < winTouchPointCount; ++i) { |
| const TOUCHINPUT &winTouchInput = winTouchInputs[i]; |
| int id = m_touchInputIDToTouchPointID.value(winTouchInput.dwID, -1); |
| if (id == -1) { |
| id = m_touchInputIDToTouchPointID.size(); |
| m_touchInputIDToTouchPointID.insert(winTouchInput.dwID, id); |
| } |
| QTouchPoint touchPoint; |
| touchPoint.pressure = 1.0; |
| touchPoint.id = id; |
| if (m_lastTouchPositions.contains(id)) |
| touchPoint.normalPosition = m_lastTouchPositions.value(id); |
| |
| const QPointF screenPos = QPointF(winTouchInput.x, winTouchInput.y) / qreal(100.); |
| if (winTouchInput.dwMask & TOUCHINPUTMASKF_CONTACTAREA) |
| touchPoint.area.setSize(QSizeF(winTouchInput.cxContact, winTouchInput.cyContact) / qreal(100.)); |
| 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 (winTouchInput.dwFlags & TOUCHEVENTF_DOWN) { |
| touchPoint.state = Qt::TouchPointPressed; |
| m_lastTouchPositions.insert(id, touchPoint.normalPosition); |
| } else if (winTouchInput.dwFlags & TOUCHEVENTF_UP) { |
| touchPoint.state = Qt::TouchPointReleased; |
| m_lastTouchPositions.remove(id); |
| } else { |
| touchPoint.state = (stationaryTouchPoint |
| ? Qt::TouchPointStationary |
| : Qt::TouchPointMoved); |
| m_lastTouchPositions.insert(id, touchPoint.normalPosition); |
| } |
| |
| allStates |= touchPoint.state; |
| |
| touchPoints.append(touchPoint); |
| } |
| |
| CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(msg.lParam)); |
| |
| // all touch points released, forget the ids we've seen, they may not be reused |
| if (allStates == Qt::TouchPointReleased) |
| m_touchInputIDToTouchPointID.clear(); |
| |
| QWindowSystemInterface::handleTouchEvent(window, |
| m_touchDevice, |
| touchPoints, |
| QWindowsKeyMapper::queryKeyboardModifiers()); |
| return true; |
| } |
| |
| bool QWindowsMouseHandler::translateGestureEvent(QWindow *window, HWND hwnd, |
| QtWindows::WindowsEventType, |
| MSG msg, LRESULT *) |
| { |
| Q_UNUSED(window) |
| Q_UNUSED(hwnd) |
| Q_UNUSED(msg) |
| return false; |
| } |
| |
| QT_END_NAMESPACE |