From 83d56811ec95dc27d095da0fb55632ea46e4bea3 Mon Sep 17 00:00:00 2001 From: Andre de la Rocha Date: Tue, 16 Oct 2018 15:06:04 +0200 Subject: Windows QPA: Fix Drag&Drop from touchscreen/pen The Drag&Drop functionality had stopped working with touchscreen/pen after the WM_POINTER-based input handling was added. The Drag&Drop functionality internally uses the DoDragDrop() WIN32 call which, according to Microsoft docs, is not supported for invocation inside handlers for touch/pen messages, and should be invoked in handlers for mouse messages that are synthesized by the OS afterwards. The result was that when DoDragDrop (which is a blocking function with its own event loop) was called it would hang ignoring all touch/pen messages until a mouse/touchpad message arrived. This change implements a workaround for this issue by enqueuing Qt touch/pen events that would be generated inside the pointer message handler, and that could start a Drag&Drop operation, and only producing them after the OS sends the associated mouse messages. Task-number: QTBUG-70887 Change-Id: Id45e0ecc70358ba250de9b3268856781ed21c9dd Reviewed-by: Friedemann Kleint --- src/plugins/platforms/windows/qwindowsdrag.cpp | 4 + src/plugins/platforms/windows/qwindowsdrag.h | 2 + .../platforms/windows/qwindowspointerhandler.cpp | 149 +++++++++++++++++++-- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index b7d225cb00..ee82b2f022 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -652,6 +652,7 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, */ bool QWindowsDrag::m_canceled = false; +bool QWindowsDrag::m_dragging = false; QWindowsDrag::QWindowsDrag() = default; @@ -699,7 +700,10 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag) const DWORD allowedEffects = translateToWinDragEffects(possibleActions); qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x" << hex << int(possibleActions) << "effects=0x" << allowedEffects << dec; + // Indicate message handlers we are in DoDragDrop() event loop. + QWindowsDrag::m_dragging = true; const HRESULT r = DoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect); + QWindowsDrag::m_dragging = false; const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect(); if (r == DRAGDROP_S_DROP) { if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) { diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h index f116e50cbf..5f30c59882 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.h +++ b/src/plugins/platforms/windows/qwindowsdrag.h @@ -92,6 +92,7 @@ public: static QWindowsDrag *instance(); void cancelDrag() override { QWindowsDrag::m_canceled = true; } static bool isCanceled() { return QWindowsDrag::m_canceled; } + static bool isDragging() { return QWindowsDrag::m_dragging; } IDataObject *dropDataObject() const { return m_dropDataObject; } void setDropDataObject(IDataObject *dataObject) { m_dropDataObject = dataObject; } @@ -102,6 +103,7 @@ public: private: static bool m_canceled; + static bool m_dragging; QWindowsDropMimeData m_dropData; IDataObject *m_dropDataObject = nullptr; diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index c5acc38e7c..09eadb247a 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -50,6 +50,9 @@ #include "qwindowswindow.h" #include "qwindowsintegration.h" #include "qwindowsscreen.h" +#if QT_CONFIG(draganddrop) +# include "qwindowsdrag.h" +#endif #include #include @@ -60,6 +63,7 @@ #include #include #include +#include #include @@ -75,6 +79,111 @@ enum { QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD }; +struct PointerTouchEventInfo { + QPointer window; + QList points; + Qt::KeyboardModifiers modifiers; +}; + +struct PointerTabletEventInfo { + QPointer window; + QPointF local; + QPointF global; + int device; + int pointerType; + Qt::MouseButtons buttons; + qreal pressure; + int xTilt; + int yTilt; + qreal tangentialPressure; + qreal rotation; + int z; + qint64 uid; + Qt::KeyboardModifiers modifiers; +}; + +static QQueue touchEventQueue; +static QQueue tabletEventQueue; + +static void enqueueTouchEvent(QWindow *window, + const QList &points, + Qt::KeyboardModifiers modifiers) +{ + PointerTouchEventInfo eventInfo; + eventInfo.window = window; + eventInfo.points = points; + eventInfo.modifiers = modifiers; + touchEventQueue.enqueue(eventInfo); +} + +static void enqueueTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, + int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, + int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, + int z, qint64 uid, Qt::KeyboardModifiers modifiers) +{ + PointerTabletEventInfo eventInfo; + eventInfo.window = window; + eventInfo.local = local; + eventInfo.global = global; + eventInfo.device = device; + eventInfo.pointerType = pointerType; + eventInfo.buttons = buttons; + eventInfo.pressure = pressure; + eventInfo.xTilt = xTilt; + eventInfo.yTilt = yTilt; + eventInfo.tangentialPressure = tangentialPressure; + eventInfo.rotation = rotation; + eventInfo.z = z; + eventInfo.uid = uid; + eventInfo.modifiers = modifiers; + tabletEventQueue.enqueue(eventInfo); +} + +static void flushTouchEvents(QTouchDevice *touchDevice) +{ + while (!touchEventQueue.isEmpty()) { + PointerTouchEventInfo eventInfo = touchEventQueue.dequeue(); + if (eventInfo.window) { + QWindowSystemInterface::handleTouchEvent(eventInfo.window, + touchDevice, + eventInfo.points, + eventInfo.modifiers); + } + } +} + +static void flushTabletEvents() +{ + while (!tabletEventQueue.isEmpty()) { + PointerTabletEventInfo eventInfo = tabletEventQueue.dequeue(); + if (eventInfo.window) { + QWindowSystemInterface::handleTabletEvent(eventInfo.window, + eventInfo.local, + eventInfo.global, + eventInfo.device, + eventInfo.pointerType, + eventInfo.buttons, + eventInfo.pressure, + eventInfo.xTilt, + eventInfo.yTilt, + eventInfo.tangentialPressure, + eventInfo.rotation, + eventInfo.z, + eventInfo.uid, + eventInfo.modifiers); + } + } +} + +static bool draggingActive() +{ +#if QT_CONFIG(draganddrop) + return QWindowsDrag::isDragging(); +#else + return false; +#endif +} + bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { *result = 0; @@ -426,6 +535,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. + if (draggingActive()) + return false; // Let DoDragDrop() loop handle it. + if (count < 1) return false; @@ -452,6 +564,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, QList touchPoints; + bool primaryPointer = false; + if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents).noquote().nospace() << showbase << __FUNCTION__ @@ -494,16 +608,23 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, touchPoint.state = stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved; m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition); } + if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) + primaryPointer = true; + touchPoints.append(touchPoint); // Avoid getting repeated messages for this frame if there are multiple pointerIds QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); } - - QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, - QWindowsKeyMapper::queryKeyboardModifiers()); - - return true; + if (primaryPointer) { + // Postpone event delivery to avoid hanging inside DoDragDrop(). + // Only the primary pointer will generate mouse messages. + enqueueTouchEvent(window, touchPoints, QWindowsKeyMapper::queryKeyboardModifiers()); + } else { + 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, @@ -512,6 +633,9 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. + if (draggingActive()) + return false; // Let DoDragDrop() loop handle it. + POINTER_PEN_INFO *penInfo = static_cast(vPenInfo); RECT pRect, dRect; @@ -592,20 +716,25 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, - pressure, xTilt, yTilt, tangentialPressure, rotation, z, - pointerId, keyModifiers); - break; + // Postpone event delivery to avoid hanging inside DoDragDrop(). + enqueueTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + pointerId, keyModifiers); + return false; // Allow mouse messages to be generated. } } return true; } -// SetCursorPos()/TrackMouseEvent() will generate old-style WM_MOUSE messages. Handle them here. +// Process old-style mouse messages here. bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { Q_UNUSED(et); + // Generate enqueued events. + flushTouchEvents(m_touchDevice); + flushTabletEvents(); + *result = 0; if (msg.message != WM_MOUSELEAVE && msg.message != WM_MOUSEMOVE) return false; -- cgit v1.2.3