From 20d6dac63c25d227ed5315801e3e853ee78ec248 Mon Sep 17 00:00:00 2001 From: Andre de la Rocha Date: Fri, 25 May 2018 14:48:03 +0200 Subject: Add Windows Pointer Input Messages support Replaces the handling of tablet/touchscreen/touchpad/mouse input with a unified implementation based on the Windows Pointer Input Messages added to Windows 8. The legacy implementation is still used for Windows 7. Task-number: QTBUG-60437 Change-Id: I0a0f48ee9d5365f84ba528aa04c6ab1fe4253c50 Reviewed-by: Friedemann Kleint --- src/plugins/platforms/windows/qtwindowsglobal.h | 25 + src/plugins/platforms/windows/qwindowscontext.cpp | 125 ++++- src/plugins/platforms/windows/qwindowscontext.h | 23 +- .../platforms/windows/qwindowsintegration.cpp | 11 +- .../platforms/windows/qwindowsintegration.h | 3 +- .../platforms/windows/qwindowsmousehandler.cpp | 49 +- .../platforms/windows/qwindowsmousehandler.h | 2 - .../platforms/windows/qwindowspointerhandler.cpp | 573 +++++++++++++++++++++ .../platforms/windows/qwindowspointerhandler.h | 82 +++ src/plugins/platforms/windows/windows.pri | 2 + 10 files changed, 831 insertions(+), 64 deletions(-) create mode 100644 src/plugins/platforms/windows/qwindowspointerhandler.cpp create mode 100644 src/plugins/platforms/windows/qwindowspointerhandler.h (limited to 'src/plugins/platforms') diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index 6c44541314..ef22a4451a 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -60,6 +60,21 @@ # define WM_DPICHANGED 0x02E0 #endif +// WM_POINTER support from Windows 8 onwards (WINVER >= 0x0602) +#ifndef WM_POINTERUPDATE +# define WM_NCPOINTERUPDATE 0x0241 +# define WM_NCPOINTERDOWN 0x0242 +# define WM_NCPOINTERUP 0x0243 +# define WM_POINTERUPDATE 0x0245 +# define WM_POINTERDOWN 0x0246 +# define WM_POINTERUP 0x0247 +# define WM_POINTERENTER 0x0249 +# define WM_POINTERLEAVE 0x024A +# define WM_POINTERACTIVATE 0x024B +# define WM_POINTERWHEEL 0x024E +# define WM_POINTERHWHEEL 0x024F +#endif // WM_POINTERUPDATE + QT_BEGIN_NAMESPACE namespace QtWindows @@ -78,6 +93,7 @@ enum ApplicationEventFlag = 0x1000000, ThemingEventFlag = 0x2000000, GenericEventFlag = 0x4000000, // Misc + PointerEventFlag = 0x8000000, }; enum WindowsEventType // Simplify event types @@ -103,13 +119,16 @@ enum WindowsEventType // Simplify event types DpiChangedEvent = WindowEventFlag + 21, EnterSizeMoveEvent = WindowEventFlag + 22, ExitSizeMoveEvent = WindowEventFlag + 23, + PointerActivateWindowEvent = WindowEventFlag + 24, MouseEvent = MouseEventFlag + 1, MouseWheelEvent = MouseEventFlag + 2, CursorEvent = MouseEventFlag + 3, TouchEvent = TouchEventFlag + 1, + PointerEvent = PointerEventFlag + 1, NonClientMouseEvent = NonClientEventFlag + MouseEventFlag + 1, NonClientHitTest = NonClientEventFlag + 2, NonClientCreate = NonClientEventFlag + 3, + NonClientPointerEvent = NonClientEventFlag + PointerEventFlag + 4, KeyEvent = KeyEventFlag + 1, KeyDownEvent = KeyEventFlag + KeyDownEventFlag + 1, KeyboardLayoutChangeEvent = KeyEventFlag + 2, @@ -167,6 +186,8 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI QtWindows::ActivateApplicationEvent : QtWindows::DeactivateApplicationEvent; case WM_MOUSEACTIVATE: return QtWindows::MouseActivateWindowEvent; + case WM_POINTERACTIVATE: + return QtWindows::PointerActivateWindowEvent; case WM_ACTIVATE: return LOWORD(wParamIn) == WA_INACTIVE ? QtWindows::DeactivateWindowEvent : QtWindows::ActivateWindowEvent; @@ -297,6 +318,10 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI if ((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) || (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONDBLCLK)) return QtWindows::MouseEvent; + if (message >= WM_NCPOINTERUPDATE && message <= WM_NCPOINTERUP) + return QtWindows::NonClientPointerEvent; + if (message >= WM_POINTERUPDATE && message <= WM_POINTERHWHEEL) + return QtWindows::PointerEvent; return QtWindows::UnknownEvent; } diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 980a9fe9ab..8d6a81036b 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -43,6 +43,7 @@ #include "qwindowswindow.h" #include "qwindowskeymapper.h" #include "qwindowsmousehandler.h" +#include "qwindowspointerhandler.h" #include "qtwindowsglobal.h" #include "qwindowsmenu.h" #include "qwindowsmime.h" @@ -193,6 +194,15 @@ void QWindowsUser32DLL::init() getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences"); setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences"); + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) { + enableMouseInPointer = (EnableMouseInPointer)library.resolve("EnableMouseInPointer"); + getPointerType = (GetPointerType)library.resolve("GetPointerType"); + getPointerInfo = (GetPointerInfo)library.resolve("GetPointerInfo"); + getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo"); + getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo"); + getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo"); + } + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) { enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); @@ -201,6 +211,11 @@ void QWindowsUser32DLL::init() } } +bool QWindowsUser32DLL::supportsPointerApi() +{ + return enableMouseInPointer && getPointerType && getPointerInfo && getPointerTouchInfo && getPointerFrameTouchInfo && getPointerPenInfo; +} + void QWindowsShcoreDLL::init() { if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1) @@ -238,6 +253,7 @@ struct QWindowsContextPrivate { int m_defaultDPI = 96; QWindowsKeyMapper m_keyMapper; QWindowsMouseHandler m_mouseHandler; + QWindowsPointerHandler m_pointerHandler; QWindowsMimeConverter m_mimeConverter; QWindowsScreenManager m_screenManager; QSharedPointer m_creationContext; @@ -255,7 +271,7 @@ QWindowsContextPrivate::QWindowsContextPrivate() QWindowsContext::user32dll.init(); QWindowsContext::shcoredll.init(); - if (m_mouseHandler.touchDevice()) + if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice()) m_systemInfo |= QWindowsContext::SI_SupportsTouch; m_displayContext = GetDC(0); m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY); @@ -280,10 +296,6 @@ QWindowsContext::QWindowsContext() : const QByteArray bv = qgetenv("QT_QPA_VERBOSE"); if (!bv.isEmpty()) QLoggingCategory::setFilterRules(QString::fromLocal8Bit(bv)); -#if QT_CONFIG(tabletevent) - d->m_tabletSupport.reset(QWindowsTabletSupport::create()); - qCDebug(lcQpaTablet) << "Tablet support: " << (d->m_tabletSupport.isNull() ? QStringLiteral("None") : d->m_tabletSupport->description()); -#endif } QWindowsContext::~QWindowsContext() @@ -309,7 +321,8 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch) return true; - QTouchDevice *touchDevice = d->m_mouseHandler.ensureTouchDevice(); + QTouchDevice *touchDevice = (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ? + d->m_pointerHandler.ensureTouchDevice() : d->m_mouseHandler.ensureTouchDevice(); if (!touchDevice) return false; @@ -329,6 +342,33 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) return true; } +bool QWindowsContext::initTablet(unsigned integrationOptions) +{ + Q_UNUSED(integrationOptions); +#if QT_CONFIG(tabletevent) + d->m_tabletSupport.reset(QWindowsTabletSupport::create()); + return true; +#else + return false; +#endif +} + +bool QWindowsContext::initPointer(unsigned integrationOptions) +{ + if (integrationOptions & QWindowsIntegration::DontUseWMPointer) + return false; + + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8) + return false; + + if (!QWindowsContext::user32dll.supportsPointerApi()) + return false; + + QWindowsContext::user32dll.enableMouseInPointer(TRUE); + d->m_systemInfo |= QWindowsContext::SI_SupportsPointer; + return true; +} + void QWindowsContext::setTabletAbsoluteRange(int a) { #if QT_CONFIG(tabletevent) @@ -631,12 +671,16 @@ QWindow *QWindowsContext::findWindow(HWND hwnd) const QWindow *QWindowsContext::windowUnderMouse() const { - return d->m_mouseHandler.windowUnderMouse(); + return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ? + d->m_pointerHandler.windowUnderMouse() : d->m_mouseHandler.windowUnderMouse(); } void QWindowsContext::clearWindowUnderMouse() { - d->m_mouseHandler.clearWindowUnderMouse(); + if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) + d->m_pointerHandler.clearWindowUnderMouse(); + else + d->m_mouseHandler.clearWindowUnderMouse(); } /*! @@ -957,7 +1001,9 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, switch (et) { case QtWindows::GestureEvent: - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); + if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); + break; case QtWindows::InputMethodOpenCandidateWindowEvent: case QtWindows::InputMethodCloseCandidateWindowEvent: // TODO: Release/regrab mouse if a popup has mouse grab. @@ -1083,19 +1129,25 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::ExposeEvent: return platformWindow->handleWmPaint(hwnd, message, wParam, lParam); case QtWindows::NonClientMouseEvent: - if (platformWindow->frameStrutEventsEnabled()) + if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); break; + case QtWindows::NonClientPointerEvent: + if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); + break; case QtWindows::EnterSizeMoveEvent: platformWindow->setFlag(QWindowsWindow::ResizeMoveActive); return true; case QtWindows::ExitSizeMoveEvent: platformWindow->clearFlag(QWindowsWindow::ResizeMoveActive); platformWindow->checkForScreenChanged(); - QWindowsMouseHandler::handleExitSizeMove(platformWindow->window()); + handleExitSizeMove(platformWindow->window()); return true; case QtWindows::ScrollEvent: - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); + if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); + break; case QtWindows::MouseWheelEvent: case QtWindows::MouseEvent: case QtWindows::LeaveEvent: @@ -1105,10 +1157,20 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, window = window->parent(); if (!window) return false; - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); + if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); + else + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result); } + break; case QtWindows::TouchEvent: - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); + if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); + break; + case QtWindows::PointerEvent: + if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); + break; case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow(). case QtWindows::FocusOutEvent: handleFocusEvent(et, platformWindow); @@ -1151,6 +1213,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, } break; case QtWindows::MouseActivateWindowEvent: + case QtWindows::PointerActivateWindowEvent: if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) { *result = LRESULT(MA_NOACTIVATE); return true; @@ -1295,6 +1358,37 @@ bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg) } #endif +void QWindowsContext::handleExitSizeMove(QWindow *window) +{ + // Windows can be moved/resized by: + // 1) User moving a window by dragging the title bar: Causes a sequence + // of WM_NCLBUTTONDOWN, WM_NCMOUSEMOVE but no WM_NCLBUTTONUP, + // leaving the left mouse button 'pressed' + // 2) User choosing Resize/Move from System menu and using mouse/cursor keys: + // No mouse events are received + // 3) Programmatically via QSizeGrip calling QPlatformWindow::startSystemResize/Move(): + // Mouse is left in pressed state after press on size grip (inside window), + // no further mouse events are received + // For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons. + const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons(); + const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons(); + if (currentButtons == appButtons) + return; + const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const QPoint globalPos = QWindowsCursor::mousePosition(); + const QPlatformWindow *platWin = window->handle(); + const QPoint localPos = platWin->mapFromGlobal(globalPos); + const QEvent::Type type = platWin->geometry().contains(globalPos) + ? QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease; + for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) { + if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) { + QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, + currentButtons, button, type, + keyboardModifiers); + } + } +} + bool QWindowsContext::asyncExpose() const { return d->m_asyncExpose; @@ -1307,7 +1401,8 @@ void QWindowsContext::setAsyncExpose(bool value) QTouchDevice *QWindowsContext::touchDevice() const { - return d->m_mouseHandler.touchDevice(); + return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ? + d->m_pointerHandler.touchDevice() : d->m_mouseHandler.touchDevice(); } static DWORD readDwordRegistrySetting(const wchar_t *regKey, const wchar_t *subKey, DWORD defaultValue) diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index cb4d049081..2c2313c30a 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -85,7 +85,14 @@ class QTouchDevice; struct QWindowsUser32DLL { inline void init(); - + inline bool supportsPointerApi(); + + typedef BOOL (WINAPI *EnableMouseInPointer)(BOOL); + typedef BOOL (WINAPI *GetPointerType)(UINT32, PVOID); + typedef BOOL (WINAPI *GetPointerInfo)(UINT32, PVOID); + typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID); + typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID); + typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID); typedef BOOL (WINAPI *SetProcessDPIAware)(); typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); @@ -95,6 +102,14 @@ struct QWindowsUser32DLL typedef int (WINAPI *GetWindowDpiAwarenessContext)(HWND); typedef int (WINAPI *GetAwarenessFromDpiAwarenessContext)(int); + // Windows pointer functions (Windows 8 or later). + EnableMouseInPointer enableMouseInPointer = nullptr; + GetPointerType getPointerType = nullptr; + GetPointerInfo getPointerInfo = nullptr; + GetPointerTouchInfo getPointerTouchInfo = nullptr; + GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr; + GetPointerPenInfo getPointerPenInfo = nullptr; + // Windows Vista onwards SetProcessDPIAware setProcessDPIAware = nullptr; @@ -134,7 +149,8 @@ public: enum SystemInfoFlags { SI_RTL_Extensions = 0x1, - SI_SupportsTouch = 0x2 + SI_SupportsTouch = 0x2, + SI_SupportsPointer = 0x4, }; // Verbose flag set by environment variable QT_QPA_VERBOSE @@ -145,6 +161,8 @@ public: bool initTouch(); bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only. + bool initTablet(unsigned integrationOptions); + bool initPointer(unsigned integrationOptions); int defaultDPI() const; @@ -220,6 +238,7 @@ private: #ifndef QT_NO_CONTEXTMENU bool handleContextMenuEvent(QWindow *window, const MSG &msg); #endif + void handleExitSizeMove(QWindow *window); void unregisterWindowClasses(); QScopedPointer d; diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 9131b323df..7194fa1d1f 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -212,6 +212,8 @@ static inline unsigned parseOptions(const QStringList ¶mList, options |= QWindowsIntegration::AlwaysUseNativeMenus; } else if (param == QLatin1String("menus=none")) { options |= QWindowsIntegration::NoNativeMenus; + } else if (param == QLatin1String("nowmpointer")) { + options |= QWindowsIntegration::DontUseWMPointer; } else { qWarning() << "Unknown option" << param; } @@ -230,8 +232,13 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList ¶mL QtWindows::ProcessDpiAwareness dpiAwareness = QtWindows::ProcessPerMonitorDpiAware; m_options = parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness); QWindowsFontDatabase::setFontOptions(m_options); - if (tabletAbsoluteRange >= 0) - m_context.setTabletAbsoluteRange(tabletAbsoluteRange); + + if (!m_context.initPointer(m_options)) { + m_context.initTablet(m_options); + if (tabletAbsoluteRange >= 0) + m_context.setTabletAbsoluteRange(tabletAbsoluteRange); + } + if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication. if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) { m_context.setProcessDpiAwareness(dpiAwareness); diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index 7ae941f7be..ef074e309e 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -66,7 +66,8 @@ public: DontUseDirectWriteFonts = QWindowsFontDatabase::DontUseDirectWriteFonts, DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts, AlwaysUseNativeMenus = 0x100, - NoNativeMenus = 0x200 + NoNativeMenus = 0x200, + DontUseWMPointer = 0x400, }; explicit QWindowsIntegration(const QStringList ¶mList); diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index add01f65cc..92aaf70e40 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -119,20 +119,16 @@ static inline void compressMouseMove(MSG *msg) static inline QTouchDevice *createTouchDevice() { - enum { QT_SM_TABLETPC = 86, QT_SM_DIGITIZER = 94, QT_SM_MAXIMUMTOUCHES = 95, - QT_NID_INTEGRATED_TOUCH = 0x1, QT_NID_EXTERNAL_TOUCH = 0x02, - QT_NID_MULTI_INPUT = 0x40, QT_NID_READY = 0x80 }; - - const int digitizers = GetSystemMetrics(QT_SM_DIGITIZER); - if (!(digitizers & (QT_NID_INTEGRATED_TOUCH | QT_NID_EXTERNAL_TOUCH))) + const int digitizers = GetSystemMetrics(SM_DIGITIZER); + if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH))) return 0; - const int tabletPc = GetSystemMetrics(QT_SM_TABLETPC); - const int maxTouchPoints = GetSystemMetrics(QT_SM_MAXIMUMTOUCHES); - qCDebug(lcQpaEvents) << "Digitizers:" << hex << showbase << (digitizers & ~QT_NID_READY) - << "Ready:" << (digitizers & QT_NID_READY) << dec << noshowbase + 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 & QT_NID_INTEGRATED_TOUCH + result->setType(digitizers & NID_INTEGRATED_TOUCH ? QTouchDevice::TouchScreen : QTouchDevice::TouchPad); QTouchDevice::Capabilities capabilities = QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition; if (result->type() == QTouchDevice::TouchPad) @@ -178,37 +174,6 @@ Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons() return result; } -void QWindowsMouseHandler::handleExitSizeMove(QWindow *window) -{ - // Windows can be moved/resized by: - // 1) User moving a window by dragging the title bar: Causes a sequence - // of WM_NCLBUTTONDOWN, WM_NCMOUSEMOVE but no WM_NCLBUTTONUP, - // leaving the left mouse button 'pressed' - // 2) User choosing Resize/Move from System menu and using mouse/cursor keys: - // No mouse events are received - // 3) Programmatically via QSizeGrip calling QPlatformWindow::startSystemResize/Move(): - // Mouse is left in pressed state after press on size grip (inside window), - // no further mouse events are received - // For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons. - const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons(); - const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons(); - if (currentButtons == appButtons) - return; - const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - const QPoint globalPos = QWindowsCursor::mousePosition(); - const QPlatformWindow *platWin = window->handle(); - const QPoint localPos = platWin->mapFromGlobal(globalPos); - const QEvent::Type type = platWin->geometry().contains(globalPos) - ? QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease; - for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) { - if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) { - QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, - currentButtons, button, type, - keyboardModifiers); - } - } -} - static QPoint lastMouseMovePos; namespace { diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.h b/src/plugins/platforms/windows/qwindowsmousehandler.h index 04d3a8bff6..480662c9bf 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.h +++ b/src/plugins/platforms/windows/qwindowsmousehandler.h @@ -80,8 +80,6 @@ public: QWindow *windowUnderMouse() const { return m_windowUnderMouse.data(); } void clearWindowUnderMouse() { m_windowUnderMouse = 0; } - static void handleExitSizeMove(QWindow *window); - private: inline bool translateMouseWheelEvent(QWindow *window, HWND hwnd, MSG msg, LRESULT *result); diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp new file mode 100644 index 0000000000..f25e6d13d8 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -0,0 +1,573 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +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: { + POINTER_INFO pointerInfo; + if (!QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo)) { + qWarning() << "GetPointerInfo() failed:" << qt_error_string(); + return false; + } + return translateMouseTouchPadEvent(window, hwnd, et, msg, &pointerInfo); + } + 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; + } + 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; + } + return translatePenEvent(window, hwnd, et, msg, &penInfo); + } + } + return false; +} + +static void getMouseEventInfo(UINT message, POINTER_BUTTON_CHANGE_TYPE changeType, QPoint globalPos, QEvent::Type *eventType, Qt::MouseButton *mouseButton) +{ + static const QHash buttonMapping { + {POINTER_CHANGE_FIRSTBUTTON_DOWN, Qt::LeftButton}, + {POINTER_CHANGE_FIRSTBUTTON_UP, Qt::LeftButton}, + {POINTER_CHANGE_SECONDBUTTON_DOWN, Qt::RightButton}, + {POINTER_CHANGE_SECONDBUTTON_UP, Qt::RightButton}, + {POINTER_CHANGE_THIRDBUTTON_DOWN, Qt::MiddleButton}, + {POINTER_CHANGE_THIRDBUTTON_UP, Qt::MiddleButton}, + {POINTER_CHANGE_FOURTHBUTTON_DOWN, Qt::XButton1}, + {POINTER_CHANGE_FOURTHBUTTON_UP, Qt::XButton1}, + {POINTER_CHANGE_FIFTHBUTTON_DOWN, Qt::XButton2}, + {POINTER_CHANGE_FIFTHBUTTON_UP, Qt::XButton2}, + }; + + static const QHash eventMapping { + {WM_POINTERUPDATE, QEvent::MouseMove}, + {WM_POINTERDOWN, QEvent::MouseButtonPress}, + {WM_POINTERUP, QEvent::MouseButtonRelease}, + {WM_NCPOINTERUPDATE, QEvent::NonClientAreaMouseMove}, + {WM_NCPOINTERDOWN, QEvent::NonClientAreaMouseButtonPress}, + {WM_NCPOINTERUP, QEvent::NonClientAreaMouseButtonRelease}, + {WM_POINTERWHEEL, QEvent::Wheel}, + {WM_POINTERHWHEEL, QEvent::Wheel}, + }; + + if (!eventType || !mouseButton) + return; + + if (message == WM_POINTERDOWN || message == WM_POINTERUP || message == WM_NCPOINTERDOWN || message == WM_NCPOINTERUP) + *mouseButton = buttonMapping.value(changeType, Qt::NoButton); + else + *mouseButton = Qt::NoButton; + + *eventType = eventMapping.value(message, QEvent::None); + + // Pointer messages lack a double click indicator. Check if this is the case here. + if (message == WM_POINTERDOWN) { + static LONG lastTime = 0; + static Qt::MouseButton lastButton = Qt::NoButton; + static QPoint lastPos; + LONG messageTime = GetMessageTime(); + if (*mouseButton == lastButton + && messageTime - lastTime < (LONG)GetDoubleClickTime() + && qAbs(globalPos.x() - lastPos.x()) < GetSystemMetrics(SM_CXDOUBLECLK) + && qAbs(globalPos.y() - lastPos.y()) < GetSystemMetrics(SM_CYDOUBLECLK)) { + *eventType = QEvent::MouseButtonDblClick; + } + lastTime = messageTime; + lastButton = *mouseButton; + lastPos = globalPos; + } +} + +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.reset(createTouchDevice()); + return m_touchDevice.data(); +} + +Qt::MouseButtons QWindowsPointerHandler::queryMouseButtons() +{ + Qt::MouseButtons result = 0; + 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; +} + +bool QWindowsPointerHandler::translateMouseTouchPadEvent(QWindow *window, HWND hwnd, + QtWindows::WindowsEventType et, + MSG msg, PVOID vPointerInfo) +{ + POINTER_INFO *pointerInfo = static_cast(vPointerInfo); + const QPoint globalPos = QPoint(pointerInfo->ptPixelLocation.x, pointerInfo->ptPixelLocation.y); + const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos); + const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const Qt::MouseButtons mouseButtons = queryMouseButtons(); + + QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos); + QWindowsWindow *platformWindow = static_cast(window->handle()); + + switch (msg.message) { + case WM_NCPOINTERDOWN: + case WM_NCPOINTERUP: + case WM_NCPOINTERUPDATE: + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERUPDATE: { + + QEvent::Type eventType; + Qt::MouseButton button; + getMouseEventInfo(msg.message, pointerInfo->ButtonChangeType, globalPos, &eventType, &button); + + if (et & QtWindows::NonClientEventFlag) { + QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, mouseButtons, button, eventType, + keyModifiers, Qt::MouseEventNotSynthesized); + return false; // To allow window dragging, etc. + } else { + if (currentWindowUnderPointer != m_windowUnderPointer) { + if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) { + QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer); + m_currentWindow = nullptr; + } + + if (currentWindowUnderPointer) { + if (currentWindowUnderPointer != m_currentWindow) { + QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, localPos, globalPos); + m_currentWindow = currentWindowUnderPointer; + if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) + wumPlatformWindow->applyCursor(); + trackLeave(hwnd); + } + } else { + platformWindow->applyCursor(); + } + m_windowUnderPointer = currentWindowUnderPointer; + } + + QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, button, eventType, + keyModifiers, Qt::MouseEventNotSynthesized); + + // The initial down click over the QSizeGrip area, which posts a resize WM_SYSCOMMAND + // has go to through DefWindowProc() for resizing to work, so we return false here, + // unless the mouse is captured, as it would mess with menu processing. + return msg.message != WM_POINTERDOWN || GetCapture(); + } + } + case WM_POINTERHWHEEL: + case WM_POINTERWHEEL: { + + int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam); + + // Qt horizontal wheel rotation orientation is opposite to the one in WM_POINTERHWHEEL + if (msg.message == WM_POINTERHWHEEL) + delta = -delta; + + const QPoint angleDelta = (msg.message == WM_POINTERHWHEEL || (keyModifiers & Qt::AltModifier)) ? + QPoint(delta, 0) : QPoint(0, delta); + + if (isValidWheelReceiver(window)) + QWindowSystemInterface::handleWheelEvent(window, localPos, globalPos, QPoint(), angleDelta, keyModifiers); + return true; + } + case WM_POINTERLEAVE: + return true; + } + return false; +} + +bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, + QtWindows::WindowsEventType et, + MSG msg, PVOID vTouchInfo, quint32 count) +{ + Q_UNUSED(hwnd); + Q_UNUSED(et); + + if (et & QtWindows::NonClientEventFlag) + return false; // Let DefWindowProc() handle Non Client messages. + + if (count < 1) + 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; + + for (quint32 i = 0; i < count; ++i) { + + QWindowSystemInterface::TouchPoint touchPoint; + touchPoint.id = touchInfo[i].pointerInfo.pointerId; + 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); + } + touchPoints.append(touchPoint); + } + + QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints, + QWindowsKeyMapper::queryKeyboardModifiers()); + + if (!(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) { + + const QPoint globalPos = QPoint(touchInfo->pointerInfo.ptPixelLocation.x, touchInfo->pointerInfo.ptPixelLocation.y); + const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos); + const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const Qt::MouseButtons mouseButtons = queryMouseButtons(); + + QEvent::Type eventType; + Qt::MouseButton button; + getMouseEventInfo(msg.message, touchInfo->pointerInfo.ButtonChangeType, globalPos, &eventType, &button); + + QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, button, eventType, + keyModifiers, Qt::MouseEventSynthesizedByQt); + } + + return true; +} + +bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, + MSG msg, PVOID vPenInfo) +{ + if (et & QtWindows::NonClientEventFlag) + return false; // Let DefWindowProc() handle Non Client messages. + + POINTER_PEN_INFO *penInfo = static_cast(vPenInfo); + const quint32 pointerId = penInfo->pointerInfo.pointerId; + const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y); + const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos); + 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; + + 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, pointerId); + 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, pointerId); + break; + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERUPDATE: { + QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(pointerId).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, globalPos, device, type, mouseButtons, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + pointerId, keyModifiers); + + QEvent::Type eventType; + Qt::MouseButton button; + getMouseEventInfo(msg.message, penInfo->pointerInfo.ButtonChangeType, globalPos, &eventType, &button); + + QWindowSystemInterface::handleMouseEvent(target, localPos, globalPos, mouseButtons, button, eventType, + keyModifiers, Qt::MouseEventSynthesizedByQt); + break; + } + } + return true; +} + +// SetCursorPos()/TrackMouseEvent() will generate old-style WM_MOUSE messages. Handle them here. +bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) +{ + Q_UNUSED(et); + + *result = 0; + if (msg.message != WM_MOUSELEAVE && msg.message != WM_MOUSEMOVE) + return false; + + const QPoint localPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); + const QPoint globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, localPos); + + QWindowsWindow *platformWindow = static_cast(window->handle()); + + if (msg.message == WM_MOUSELEAVE) { + if (window == m_currentWindow) { + QWindowSystemInterface::handleLeaveEvent(window); + m_windowUnderPointer = nullptr; + m_currentWindow = nullptr; + platformWindow->applyCursor(); + } + return false; + } + + // 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. + static QPoint lastMouseMovePos; + const bool discardEvent = msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos); + lastMouseMovePos = globalPos; + + QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos); + + if (currentWindowUnderPointer != m_windowUnderPointer) { + if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) { + QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer); + m_currentWindow = nullptr; + } + + if (currentWindowUnderPointer) { + if (currentWindowUnderPointer != m_currentWindow) { + QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, localPos, globalPos); + m_currentWindow = currentWindowUnderPointer; + if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) + wumPlatformWindow->applyCursor(); + trackLeave(hwnd); + } + } else { + platformWindow->applyCursor(); + } + m_windowUnderPointer = currentWindowUnderPointer; + } + + const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const Qt::MouseButtons mouseButtons = queryMouseButtons(); + + if (!discardEvent) + QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, Qt::NoButton, QEvent::MouseMove, + keyModifiers, Qt::MouseEventNotSynthesized); + return false; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.h b/src/plugins/platforms/windows/qwindowspointerhandler.h new file mode 100644 index 0000000000..11bc9419d7 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowspointerhandler.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSPOINTERHANDLER_H +#define QWINDOWSPOINTERHANDLER_H + +#include "qtwindowsglobal.h" +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWindow; +class QTouchDevice; + +class QWindowsPointerHandler +{ + Q_DISABLE_COPY(QWindowsPointerHandler) +public: + QWindowsPointerHandler() = default; + 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); + QTouchDevice *touchDevice() const { return m_touchDevice.data(); } + QTouchDevice *ensureTouchDevice(); + Qt::MouseButtons queryMouseButtons(); + QWindow *windowUnderMouse() const { return m_windowUnderPointer.data(); } + void clearWindowUnderMouse() { m_windowUnderPointer = nullptr; } + +private: + bool translateMouseTouchPadEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPointerInfo); + bool translateTouchEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vTouchInfo, unsigned int count); + bool translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPenInfo); + + QScopedPointer m_touchDevice; + QHash m_lastTouchPositions; + QPointer m_windowUnderPointer; + QPointer m_currentWindow; + bool m_needsEnterOnPointerUpdate = false; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSPOINTERHANDLER_H diff --git a/src/plugins/platforms/windows/windows.pri b/src/plugins/platforms/windows/windows.pri index e2129b7c9d..c1d4e907d9 100644 --- a/src/plugins/platforms/windows/windows.pri +++ b/src/plugins/platforms/windows/windows.pri @@ -18,6 +18,7 @@ SOURCES += \ $$PWD/qwindowsscreen.cpp \ $$PWD/qwindowskeymapper.cpp \ $$PWD/qwindowsmousehandler.cpp \ + $$PWD/qwindowspointerhandler.cpp \ $$PWD/qwindowsole.cpp \ $$PWD/qwindowsdropdataobject.cpp \ $$PWD/qwindowsmime.cpp \ @@ -40,6 +41,7 @@ HEADERS += \ $$PWD/qwindowsscreen.h \ $$PWD/qwindowskeymapper.h \ $$PWD/qwindowsmousehandler.h \ + $$PWD/qwindowspointerhandler.h \ $$PWD/qtwindowsglobal.h \ $$PWD/qwindowsole.h \ $$PWD/qwindowsdropdataobject.h \ -- cgit v1.2.3