qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
Dmitry Kazakov 7e5b35935e Fix generation of Leave events when using tablet devices
When both mouse and tablet events are handled by QWindowsPointerHandler,
m_currentWindow variable is shared among the two event streams, therefore
each stream should ensure it does equivalent operations, when changing it.

Here we should subscribe to the Leave events, when we emit Enter event
from the inside of the tablet events flow. Without whis subscription,
the cursor may stuck in "resize" state when crossing the window's
frame multiple times.

Change-Id: I88df4a42ae86243e10ecd4a4cedf87639c96d169
Reviewed-by: Andre de la Rocha <andre.rocha@qt.io>
2019-05-07 09:56:02 +00:00

765 lines
33 KiB
C++

/****************************************************************************
**
** 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 <QtGui/qguiapplication.h>
#include <QtGui/qscreen.h>
#include <QtGui/qtouchdevice.h>
#include <QtGui/qwindow.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/qqueue.h>
#include <algorithm>
#include <windowsx.h>
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<POINTER_TOUCH_INFO, 10> 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<POINTER_PEN_INFO, 10> 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<QWindowsWindow *>(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<QWindowsWindow *>(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<POINTER_TOUCH_INFO *>(vTouchInfo);
const QRect screenGeometry = screen->geometry();
QList<QWindowSystemInterface::TouchPoint> 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<POINTER_PEN_INFO *>(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) {
// make sure we subscribe to leave events for this window
trackLeave(hwnd);
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