Windows QPA: Remove tablet->mouse synth and fix Drag&Drop with touch/pen

This patch avoids synthesizing mouse messages in the QPA for touch/pen
input, and lets the GUI do mouse event synthesis for unhandled touch/pen
events, if required, like in other platforms. This requires a workaround
to avoid breaking drag and drop with touch/pen (or making it worse). DnD
on Windows is based on the DoDragDrop() Win32 API, which does not work
with touch/pen input, which in some cases cause a DnD operation started
with touch/pen to hang until the mouse is moved. To avoid it we process
pointer messages for touch/pen and generate mouse input through
SendInput() to trigger DoDragDrop(), which then seems to work as
expected.

So now we inform QtGui that the Windows platform no longer sends
synth-mouse events after tablet events (unsetting the flag added in
f931e5e72d4617023bbea46cba2c0d61bb1efa4f); this completes what was
attempted in 8ada0633cd58e0608df2e16880f1293286025504.

Fixes: QTBUG-106368
Fixes: QTBUG-57577
Fixes: QTBUG-100788
Task-number: QTBUG-77414
Task-number: QTBUG-104594
Change-Id: I46db3c74be2a95cf2d94ba930398e58dc930d2db
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
(cherry picked from commit 31e7790102b260344893eaa8bf8b7b1a0f95e3b7)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
André de la Rocha 2022-10-13 20:10:13 +02:00 committed by Qt Cherry-pick Bot
parent 1515422d68
commit 97c95f606f
3 changed files with 82 additions and 3 deletions

View File

@ -644,6 +644,84 @@ IDropTargetHelper* QWindowsDrag::dropHelper() {
return m_cachedDropTargetHelper;
}
// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
{
HWND hwnd = ::GetFocus();
bool starting = false;
for (;;) {
MSG msg{};
if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
if (msg.message == WM_MOUSEMOVE) {
// Only consider the first simulated event, or actual mouse messages.
if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
return E_FAIL;
return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
}
if (msg.message == WM_POINTERUPDATE) {
const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
POINTER_INFO pointerInfo{};
if (!GetPointerInfo(pointerId, &pointerInfo))
return E_FAIL;
if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
flags |= MOUSEEVENTF_LEFTDOWN;
if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
flags |= MOUSEEVENTF_RIGHTDOWN;
if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
flags |= MOUSEEVENTF_MIDDLEDOWN;
if (!starting) {
POINT pt{};
if (::GetCursorPos(&pt)) {
// Send mouse input that can generate a WM_MOUSEMOVE message.
if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
&& (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
INPUT input{};
input.type = INPUT_MOUSE;
input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
input.mi.dwFlags = flags;
::SendInput(1, &input, sizeof(input));
starting = true;
}
}
}
}
} else {
// Handle other messages.
qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
if (msg.message == WM_POINTERLEAVE)
return E_FAIL;
}
} else {
return E_FAIL;
}
}
}
Qt::DropAction QWindowsDrag::drag(QDrag *drag)
{
// TODO: Accessibility handling?
@ -661,7 +739,7 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag)
<< Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
// Indicate message handlers we are in DoDragDrop() event loop.
QWindowsDrag::m_dragging = true;
const HRESULT r = DoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
QWindowsDrag::m_dragging = false;
const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
if (r == DRAGDROP_S_DROP) {

View File

@ -223,6 +223,7 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr
QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents);
else
m_context.initTablet();
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication.
if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) {

View File

@ -798,8 +798,8 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
break;
case QT_PT_PEN:
#if QT_CONFIG(tabletevent)
if (!m_activeTabletDevice.isNull())
device = m_activeTabletDevice.data();
qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
return false;
#endif
break;
}