Skip to content

Commit

Permalink
Windows QPA: Fix QWheelEvent::buttons() after click on title bar
Browse files Browse the repository at this point in the history
When the left or right mouse buttons are pressed over the window title
bar a WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN message is received. But when the
button is released, no corresponding UP message is received, but only
a WM_NCMOUSEMOVE or WM_MOUSEMOVE. This makes the internal mouse button
state stored in QGuiApplication get out of sync with the actual state,
resulting in an incorrect button state being used in QWheelEvent.
This patch detects the button release condition and generates the missing
release event.

Change-Id: I6dd9f8580bd6ba772522574f9a08298e49c43e61
Fixes: QTBUG-75678
Reviewed-by: Friedemann Kleint <[email protected]>
  • Loading branch information
Andre de la Rocha committed May 18, 2019
1 parent a96a33d commit 8be17f1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 15 deletions.
4 changes: 4 additions & 0 deletions src/plugins/platforms/windows/qwindowscontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,10 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
keyboardModifiers);
}
}
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
d->m_pointerHandler.clearEvents();
else
d->m_mouseHandler.clearEvents();
}

bool QWindowsContext::asyncExpose() const
Expand Down
45 changes: 37 additions & 8 deletions src/plugins/platforms/windows/qwindowsmousehandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ QTouchDevice *QWindowsMouseHandler::ensureTouchDevice()
return m_touchDevice;
}

void QWindowsMouseHandler::clearEvents()
{
m_lastEventType = QEvent::None;
m_lastEventButton = Qt::NoButton;
}

Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons()
{
Qt::MouseButtons result = nullptr;
Expand Down Expand Up @@ -287,8 +293,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,

Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;

const MouseEvent mouseEvent = eventFromMsg(msg);

// Check for events synthesized from touch. Lower byte is touch index, 0 means pen.
static const bool passSynthesizedMouseEvents =
!(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch);
Expand All @@ -305,13 +309,40 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}
}

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) {
const Qt::MouseButtons buttons = QWindowsMouseHandler::queryMouseButtons();
QWindowSystemInterface::handleFrameStrutMouseEvent(window, clientPosition,
globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
QWindowsKeyMapper::queryKeyboardModifiers(),
source);
keyModifiers, source);
return false; // Allow further event processing (dragging of windows).
}

Expand All @@ -334,7 +365,6 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}

QWindowsWindow *platformWindow = static_cast<QWindowsWindow *>(window->handle());
const Qt::MouseButtons buttons = keyStateToMouseButtons(int(msg.wParam));

// 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,
Expand Down Expand Up @@ -461,8 +491,7 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
if (!discardEvent && mouseEvent.type != QEvent::None) {
QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
QWindowsKeyMapper::queryKeyboardModifiers(),
source);
keyModifiers, source);
}
m_previousCaptureWindow = hasCapture ? window : nullptr;
// QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/platforms/windows/qwindowsmousehandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

#include <QtCore/qpointer.h>
#include <QtCore/qhash.h>
#include <QtGui/qevent.h>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -72,13 +73,14 @@ class QWindowsMouseHandler
bool translateScrollEvent(QWindow *window, HWND hwnd,
MSG msg, LRESULT *result);

static inline Qt::MouseButtons keyStateToMouseButtons(int);
static inline Qt::MouseButtons keyStateToMouseButtons(WPARAM);
static inline Qt::KeyboardModifiers keyStateToModifiers(int);
static inline int mouseButtonsToKeyState(Qt::MouseButtons);

static Qt::MouseButtons queryMouseButtons();
QWindow *windowUnderMouse() const { return m_windowUnderMouse.data(); }
void clearWindowUnderMouse() { m_windowUnderMouse = 0; }
void clearEvents();

private:
inline bool translateMouseWheelEvent(QWindow *window, HWND hwnd,
Expand All @@ -91,9 +93,11 @@ class QWindowsMouseHandler
QTouchDevice *m_touchDevice = nullptr;
bool m_leftButtonDown = false;
QWindow *m_previousCaptureWindow = nullptr;
QEvent::Type m_lastEventType = QEvent::None;
Qt::MouseButton m_lastEventButton = Qt::NoButton;
};

Qt::MouseButtons QWindowsMouseHandler::keyStateToMouseButtons(int wParam)
Qt::MouseButtons QWindowsMouseHandler::keyStateToMouseButtons(WPARAM wParam)
{
Qt::MouseButtons mb(Qt::NoButton);
if (wParam & MK_LBUTTON)
Expand Down
37 changes: 33 additions & 4 deletions src/plugins/platforms/windows/qwindowspointerhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ QTouchDevice *QWindowsPointerHandler::ensureTouchDevice()
return m_touchDevice;
}

void QWindowsPointerHandler::clearEvents()
{
m_lastEventType = QEvent::None;
m_lastEventButton = Qt::NoButton;
}

void QWindowsPointerHandler::handleCaptureRelease(QWindow *window,
QWindow *currentWindowUnderPointer,
HWND hwnd,
Expand Down Expand Up @@ -726,10 +732,35 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
}

const MouseEvent mouseEvent = eventFromMsg(msg);
Qt::MouseButtons mouseButtons;

if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
mouseButtons = queryMouseButtons();
else
mouseButtons = mouseButtonsFromKeyState(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 & mouseButtons) == 0) {
if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, mouseButtons, m_lastEventButton,
QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
} else {
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, m_lastEventButton,
QEvent::MouseButtonRelease, keyModifiers, source);
}
}
m_lastEventType = mouseEvent.type;
m_lastEventButton = mouseEvent.button;

if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
const Qt::MouseButtons nonclientButtons = queryMouseButtons();
QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, nonclientButtons,
QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, mouseButtons,
mouseEvent.button, mouseEvent.type, keyModifiers, source);
return false; // Allow further event processing
}
Expand All @@ -745,8 +776,6 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
return true;
}

const Qt::MouseButtons mouseButtons = mouseButtonsFromKeyState(msg.wParam);

handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons);
handleEnterLeave(window, currentWindowUnderPointer, globalPos);

Expand Down
5 changes: 4 additions & 1 deletion src/plugins/platforms/windows/qwindowspointerhandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
#include <QtCore/qpointer.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qhash.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtGui/qevent.h>

QT_BEGIN_NAMESPACE

Expand All @@ -64,6 +64,7 @@ class QWindowsPointerHandler
QTouchDevice *ensureTouchDevice();
QWindow *windowUnderMouse() const { return m_windowUnderPointer.data(); }
void clearWindowUnderMouse() { m_windowUnderPointer = nullptr; }
void clearEvents();

private:
bool translateTouchEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vTouchInfo, unsigned int count);
Expand All @@ -79,6 +80,8 @@ class QWindowsPointerHandler
QPointer<QWindow> m_currentWindow;
QWindow *m_previousCaptureWindow = nullptr;
bool m_needsEnterOnPointerUpdate = false;
QEvent::Type m_lastEventType = QEvent::None;
Qt::MouseButton m_lastEventButton = Qt::NoButton;
};

QT_END_NAMESPACE
Expand Down

0 comments on commit 8be17f1

Please sign in to comment.