1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qwindowsmousehandler.h"
41 #include "qwindowskeymapper.h"
42 #include "qwindowscontext.h"
43 #include "qwindowswindow.h"
44 #include "qwindowsintegration.h"
45 #include "qwindowsscreen.h"
46 
47 #include <qpa/qwindowsysteminterface.h>
48 #include <QtGui/qguiapplication.h>
49 #include <QtGui/qscreen.h>
50 #include <QtGui/qtouchdevice.h>
51 #include <QtGui/qwindow.h>
52 #include <QtGui/qcursor.h>
53 
54 #include <QtCore/qdebug.h>
55 #include <QtCore/qscopedpointer.h>
56 
57 #include <windowsx.h>
58 
59 QT_BEGIN_NAMESPACE
60 
compressMouseMove(MSG * msg)61 static inline void compressMouseMove(MSG *msg)
62 {
63     // Compress mouse move events
64     if (msg->message == WM_MOUSEMOVE) {
65         MSG mouseMsg;
66         while (PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEFIRST,
67                            WM_MOUSELAST, PM_NOREMOVE)) {
68             if (mouseMsg.message == WM_MOUSEMOVE) {
69 #define PEEKMESSAGE_IS_BROKEN 1
70 #ifdef PEEKMESSAGE_IS_BROKEN
71                 // Since the Windows PeekMessage() function doesn't
72                 // correctly return the wParam for WM_MOUSEMOVE events
73                 // if there is a key release event in the queue
74                 // _before_ the mouse event, we have to also consider
75                 // key release events (kls 2003-05-13):
76                 MSG keyMsg;
77                 bool done = false;
78                 while (PeekMessage(&keyMsg, nullptr, WM_KEYFIRST, WM_KEYLAST,
79                                    PM_NOREMOVE)) {
80                     if (keyMsg.time < mouseMsg.time) {
81                         if ((keyMsg.lParam & 0xC0000000) == 0x40000000) {
82                             PeekMessage(&keyMsg, nullptr, keyMsg.message,
83                                         keyMsg.message, PM_REMOVE);
84                         } else {
85                             done = true;
86                             break;
87                         }
88                     } else {
89                         break; // no key event before the WM_MOUSEMOVE event
90                     }
91                 }
92                 if (done)
93                     break;
94 #else
95                 // Actually the following 'if' should work instead of
96                 // the above key event checking, but apparently
97                 // PeekMessage() is broken :-(
98                 if (mouseMsg.wParam != msg.wParam)
99                     break; // leave the message in the queue because
100                 // the key state has changed
101 #endif
102                 // Update the passed in MSG structure with the
103                 // most recent one.
104                 msg->lParam = mouseMsg.lParam;
105                 msg->wParam = mouseMsg.wParam;
106                 // Extract the x,y coordinates from the lParam as we do in the WndProc
107                 msg->pt.x = GET_X_LPARAM(mouseMsg.lParam);
108                 msg->pt.y = GET_Y_LPARAM(mouseMsg.lParam);
109                 clientToScreen(msg->hwnd, &(msg->pt));
110                 // Remove the mouse move message
111                 PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEMOVE,
112                             WM_MOUSEMOVE, PM_REMOVE);
113             } else {
114                 break; // there was no more WM_MOUSEMOVE event
115             }
116         }
117     }
118 }
119 
createTouchDevice()120 static inline QTouchDevice *createTouchDevice()
121 {
122     const int digitizers = GetSystemMetrics(SM_DIGITIZER);
123     if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH)))
124         return nullptr;
125     const int tabletPc = GetSystemMetrics(SM_TABLETPC);
126     const int maxTouchPoints = GetSystemMetrics(SM_MAXIMUMTOUCHES);
127     qCDebug(lcQpaEvents) << "Digitizers:" << Qt::hex << Qt::showbase << (digitizers & ~NID_READY)
128         << "Ready:" << (digitizers & NID_READY) << Qt::dec << Qt::noshowbase
129         << "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints;
130     auto *result = new QTouchDevice;
131     result->setType(digitizers & NID_INTEGRATED_TOUCH
132                     ? QTouchDevice::TouchScreen : QTouchDevice::TouchPad);
133     QTouchDevice::Capabilities capabilities = QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition;
134     if (result->type() == QTouchDevice::TouchPad)
135         capabilities |= QTouchDevice::MouseEmulation;
136     result->setCapabilities(capabilities);
137     result->setMaximumTouchPoints(maxTouchPoints);
138     return result;
139 }
140 
141 /*!
142     \class QWindowsMouseHandler
143     \brief Windows mouse handler
144 
145     Dispatches mouse and touch events. Separate for code cleanliness.
146 
147     \internal
148 */
149 
150 QWindowsMouseHandler::QWindowsMouseHandler() = default;
151 
ensureTouchDevice()152 QTouchDevice *QWindowsMouseHandler::ensureTouchDevice()
153 {
154     if (!m_touchDevice)
155         m_touchDevice = createTouchDevice();
156     return m_touchDevice;
157 }
158 
clearEvents()159 void QWindowsMouseHandler::clearEvents()
160 {
161     m_lastEventType = QEvent::None;
162     m_lastEventButton = Qt::NoButton;
163 }
164 
queryMouseButtons()165 Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons()
166 {
167     Qt::MouseButtons result;
168     const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON);
169     if (GetAsyncKeyState(VK_LBUTTON) < 0)
170         result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton;
171     if (GetAsyncKeyState(VK_RBUTTON) < 0)
172         result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton;
173     if (GetAsyncKeyState(VK_MBUTTON) < 0)
174         result |= Qt::MiddleButton;
175     if (GetAsyncKeyState(VK_XBUTTON1) < 0)
176         result |= Qt::XButton1;
177     if (GetAsyncKeyState(VK_XBUTTON2) < 0)
178         result |= Qt::XButton2;
179     return result;
180 }
181 
182 static QPoint lastMouseMovePos;
183 
184 namespace {
185 struct MouseEvent {
186     QEvent::Type type;
187     Qt::MouseButton button;
188 };
189 
190 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug d,const MouseEvent & e)191 QDebug operator<<(QDebug d, const MouseEvent &e)
192 {
193     QDebugStateSaver saver(d);
194     d.nospace();
195     d << "MouseEvent(" << e.type << ", " << e.button << ')';
196     return d;
197 }
198 #endif // QT_NO_DEBUG_STREAM
199 } // namespace
200 
extraButton(WPARAM wParam)201 static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON...
202 {
203     return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton;
204 }
205 
eventFromMsg(const MSG & msg)206 static inline MouseEvent eventFromMsg(const MSG &msg)
207 {
208     switch (msg.message) {
209     case WM_MOUSEMOVE:
210         return {QEvent::MouseMove, Qt::NoButton};
211     case WM_LBUTTONDOWN:
212         return {QEvent::MouseButtonPress, Qt::LeftButton};
213     case WM_LBUTTONUP:
214         return {QEvent::MouseButtonRelease, Qt::LeftButton};
215     case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press
216         return {QEvent::MouseButtonPress, Qt::LeftButton};
217     case WM_MBUTTONDOWN:
218         return {QEvent::MouseButtonPress, Qt::MiddleButton};
219     case WM_MBUTTONUP:
220         return {QEvent::MouseButtonRelease, Qt::MiddleButton};
221     case WM_MBUTTONDBLCLK:
222         return {QEvent::MouseButtonPress, Qt::MiddleButton};
223     case WM_RBUTTONDOWN:
224         return {QEvent::MouseButtonPress, Qt::RightButton};
225     case WM_RBUTTONUP:
226         return {QEvent::MouseButtonRelease, Qt::RightButton};
227     case WM_RBUTTONDBLCLK:
228         return {QEvent::MouseButtonPress, Qt::RightButton};
229     case WM_XBUTTONDOWN:
230         return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
231     case WM_XBUTTONUP:
232         return {QEvent::MouseButtonRelease, extraButton(msg.wParam)};
233     case WM_XBUTTONDBLCLK:
234         return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
235     case WM_NCMOUSEMOVE:
236         return {QEvent::NonClientAreaMouseMove, Qt::NoButton};
237     case WM_NCLBUTTONDOWN:
238         return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
239     case WM_NCLBUTTONUP:
240         return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton};
241     case WM_NCLBUTTONDBLCLK:
242         return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
243     case WM_NCMBUTTONDOWN:
244         return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
245     case WM_NCMBUTTONUP:
246         return {QEvent::NonClientAreaMouseButtonRelease, Qt::MiddleButton};
247     case WM_NCMBUTTONDBLCLK:
248         return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
249     case WM_NCRBUTTONDOWN:
250         return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
251     case WM_NCRBUTTONUP:
252         return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton};
253     case WM_NCRBUTTONDBLCLK:
254         return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
255     default: // WM_MOUSELEAVE
256         break;
257     }
258     return {QEvent::None, Qt::NoButton};
259 }
260 
translateMouseEvent(QWindow * window,HWND hwnd,QtWindows::WindowsEventType et,MSG msg,LRESULT * result)261 bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
262                                                QtWindows::WindowsEventType et,
263                                                MSG msg, LRESULT *result)
264 {
265     enum : quint64 { signatureMask = 0xffffff00, miWpSignature = 0xff515700 };
266 
267     if (et == QtWindows::MouseWheelEvent)
268         return translateMouseWheelEvent(window, hwnd, msg, result);
269 
270     QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
271     if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd))  {
272         RECT clientArea;
273         GetClientRect(hwnd, &clientArea);
274         winEventPosition.setX(clientArea.right - winEventPosition.x());
275     }
276 
277     QPoint clientPosition;
278     QPoint globalPosition;
279     if (et & QtWindows::NonClientEventFlag) {
280         globalPosition = winEventPosition;
281         clientPosition = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPosition);
282     } else {
283         clientPosition = winEventPosition;
284         globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition);
285     }
286 
287     // Windows sends a mouse move with no buttons pressed to signal "Enter"
288     // when a window is shown over the cursor. Discard the event and only use
289     // it for generating QEvent::Enter to be consistent with other platforms -
290     // X11 and macOS.
291     bool discardEvent = false;
292     if (msg.message == WM_MOUSEMOVE) {
293         const bool samePosition = globalPosition == lastMouseMovePos;
294         lastMouseMovePos = globalPosition;
295         if (msg.wParam == 0 && (m_windowUnderMouse.isNull() || samePosition))
296             discardEvent = true;
297     }
298 
299     Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
300 
301     // Check for events synthesized from touch. Lower byte is touch index, 0 means pen.
302     static const bool passSynthesizedMouseEvents =
303             !(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch);
304     // Check for events synthesized from touch. Lower 7 bits are touch/pen index, bit 8 indicates touch.
305     // However, when tablet support is active, extraInfo is a packet serial number. This is not a problem
306     // since we do not want to ignore mouse events coming from a tablet.
307     // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320.aspx
308     const auto extraInfo = quint64(GetMessageExtraInfo());
309     if ((extraInfo & signatureMask) == miWpSignature) {
310         if (extraInfo & 0x80) { // Bit 7 indicates touch event, else tablet pen.
311             source = Qt::MouseEventSynthesizedBySystem;
312             if (!passSynthesizedMouseEvents)
313                 return false;
314         }
315     }
316 
317     const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
318     const MouseEvent mouseEvent = eventFromMsg(msg);
319     Qt::MouseButtons buttons;
320 
321     if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
322         buttons = queryMouseButtons();
323     else
324         buttons = keyStateToMouseButtons(msg.wParam);
325 
326     // When the left/right mouse buttons are pressed over the window title bar
327     // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP
328     // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE.
329     // We detect it and generate the missing release events here. (QTBUG-75678)
330     // The last event vars are cleared on QWindowsContext::handleExitSizeMove()
331     // to avoid generating duplicated release events.
332     if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
333             && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
334             && (m_lastEventButton & buttons) == 0) {
335             if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
336                 QWindowSystemInterface::handleFrameStrutMouseEvent(window, clientPosition, globalPosition, buttons, m_lastEventButton,
337                                                                    QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
338             } else {
339                 QWindowSystemInterface::handleMouseEvent(window, clientPosition, globalPosition, buttons, m_lastEventButton,
340                                                          QEvent::MouseButtonRelease, keyModifiers, source);
341             }
342     }
343     m_lastEventType = mouseEvent.type;
344     m_lastEventButton = mouseEvent.button;
345 
346     if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
347         QWindowSystemInterface::handleFrameStrutMouseEvent(window, clientPosition,
348                                                            globalPosition, buttons,
349                                                            mouseEvent.button, mouseEvent.type,
350                                                            keyModifiers, source);
351         return false; // Allow further event processing (dragging of windows).
352     }
353 
354     *result = 0;
355     if (msg.message == WM_MOUSELEAVE) {
356         qCDebug(lcQpaEvents) << mouseEvent << "for" << window << "previous window under mouse="
357             << m_windowUnderMouse << "tracked window=" << m_trackedWindow;
358 
359         // When moving out of a window, WM_MOUSEMOVE within the moved-to window is received first,
360         // so if m_trackedWindow is not the window here, it means the cursor has left the
361         // application.
362         if (window == m_trackedWindow) {
363             QWindow *leaveTarget = m_windowUnderMouse ? m_windowUnderMouse : m_trackedWindow;
364             qCDebug(lcQpaEvents) << "Generating leave event for " << leaveTarget;
365             QWindowSystemInterface::handleLeaveEvent(leaveTarget);
366             m_trackedWindow = nullptr;
367             m_windowUnderMouse = nullptr;
368         }
369         return true;
370     }
371 
372     auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
373 
374     // If the window was recently resized via mouse doubleclick on the frame or title bar,
375     // we don't get WM_LBUTTONDOWN or WM_LBUTTONDBLCLK for the second click,
376     // but we will get at least one WM_MOUSEMOVE with left button down and the WM_LBUTTONUP,
377     // which will result undesired mouse press and release events.
378     // To avoid those, we ignore any events with left button down if we didn't
379     // get the original WM_LBUTTONDOWN/WM_LBUTTONDBLCLK.
380     if (msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONDBLCLK) {
381         m_leftButtonDown = true;
382     } else {
383         const bool actualLeftDown = buttons & Qt::LeftButton;
384         if (!m_leftButtonDown && actualLeftDown) {
385             // Autocapture the mouse for current window to and ignore further events until release.
386             // Capture is necessary so we don't get WM_MOUSELEAVEs to confuse matters.
387             // This autocapture is released normally when button is released.
388             if (!platformWindow->hasMouseCapture()) {
389                 platformWindow->applyCursor();
390                 platformWindow->setMouseGrabEnabled(true);
391                 platformWindow->setFlag(QWindowsWindow::AutoMouseCapture);
392                 qCDebug(lcQpaEvents) << "Automatic mouse capture for missing buttondown event" << window;
393             }
394             m_previousCaptureWindow = window;
395             return true;
396         }
397         if (m_leftButtonDown && !actualLeftDown)
398             m_leftButtonDown = false;
399     }
400 
401     // In this context, neither an invisible nor a transparent window (transparent regarding mouse
402     // events, "click-through") can be considered as the window under mouse.
403     QWindow *currentWindowUnderMouse = platformWindow->hasMouseCapture() ?
404         QWindowsScreen::windowAt(globalPosition, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window;
405     while (currentWindowUnderMouse && currentWindowUnderMouse->flags() & Qt::WindowTransparentForInput)
406         currentWindowUnderMouse = currentWindowUnderMouse->parent();
407     // QTBUG-44332: When Qt is running at low integrity level and
408     // a Qt Window is parented on a Window of a higher integrity process
409     // using QWindow::fromWinId() (for example, Qt running in a browser plugin)
410     // ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED)
411     if (!currentWindowUnderMouse) {
412         const QRect clientRect(QPoint(0, 0), window->size());
413         if (clientRect.contains(winEventPosition))
414             currentWindowUnderMouse = window;
415     }
416 
417     compressMouseMove(&msg);
418     // Qt expects the platform plugin to capture the mouse on
419     // any button press until release.
420     if (!platformWindow->hasMouseCapture()
421         && (mouseEvent.type == QEvent::MouseButtonPress || mouseEvent.type == QEvent::MouseButtonDblClick)) {
422         platformWindow->setMouseGrabEnabled(true);
423         platformWindow->setFlag(QWindowsWindow::AutoMouseCapture);
424         qCDebug(lcQpaEvents) << "Automatic mouse capture " << window;
425         // Implement "Click to focus" for native child windows (unless it is a native widget window).
426         if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window)
427             window->requestActivate();
428     } else if (platformWindow->hasMouseCapture()
429                && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)
430                && mouseEvent.type == QEvent::MouseButtonRelease
431                && !buttons) {
432         platformWindow->setMouseGrabEnabled(false);
433         qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window;
434     }
435 
436     const bool hasCapture = platformWindow->hasMouseCapture();
437     const bool currentNotCapturing = hasCapture && currentWindowUnderMouse != window;
438     // Enter new window: track to generate leave event.
439     // If there is an active capture, only track if the current window is capturing,
440     // so we don't get extra leave when cursor leaves the application.
441     if (window != m_trackedWindow && !currentNotCapturing) {
442         TRACKMOUSEEVENT tme;
443         tme.cbSize = sizeof(TRACKMOUSEEVENT);
444         tme.dwFlags = TME_LEAVE;
445         tme.hwndTrack = hwnd;
446         tme.dwHoverTime = HOVER_DEFAULT; //
447         if (!TrackMouseEvent(&tme))
448             qWarning("TrackMouseEvent failed.");
449         m_trackedWindow =  window;
450     }
451 
452     // No enter or leave events are sent as long as there is an autocapturing window.
453     if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
454         // Leave is needed if:
455         // 1) There is no capture and we move from a window to another window.
456         //    Note: Leaving the application entirely is handled in WM_MOUSELEAVE case.
457         // 2) There is capture and we move out of the capturing window.
458         // 3) There is a new capture and we were over another window.
459         if ((m_windowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse
460                 && (!hasCapture || window == m_windowUnderMouse))
461             || (hasCapture && m_previousCaptureWindow != window && m_windowUnderMouse
462                 && m_windowUnderMouse != window)) {
463             qCDebug(lcQpaEvents) << "Synthetic leave for " << m_windowUnderMouse;
464             QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse);
465             if (currentNotCapturing) {
466                 // Clear tracking if capturing and current window is not the capturing window
467                 // to avoid leave when mouse actually leaves the application.
468                 m_trackedWindow = nullptr;
469                 // We are not officially in any window, but we need to set some cursor to clear
470                 // whatever cursor the left window had, so apply the cursor of the capture window.
471                 platformWindow->applyCursor();
472             }
473         }
474         // Enter is needed if:
475         // 1) There is no capture and we move to a new window.
476         // 2) There is capture and we move into the capturing window.
477         // 3) The capture just ended and we are over non-capturing window.
478         if ((currentWindowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse
479                 && (!hasCapture || currentWindowUnderMouse == window))
480             || (m_previousCaptureWindow && window != m_previousCaptureWindow && currentWindowUnderMouse
481                 && currentWindowUnderMouse != m_previousCaptureWindow)) {
482             QPoint localPosition;
483             qCDebug(lcQpaEvents) << "Entering " << currentWindowUnderMouse;
484             if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderMouse)) {
485                 localPosition = wumPlatformWindow->mapFromGlobal(globalPosition);
486                 wumPlatformWindow->applyCursor();
487             }
488             QWindowSystemInterface::handleEnterEvent(currentWindowUnderMouse, localPosition, globalPosition);
489         }
490         // We need to track m_windowUnderMouse separately from m_trackedWindow, as
491         // Windows mouse tracking will not trigger WM_MOUSELEAVE for leaving window when
492         // mouse capture is set.
493         m_windowUnderMouse = currentWindowUnderMouse;
494     }
495 
496     if (!discardEvent && mouseEvent.type != QEvent::None) {
497         QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition, buttons,
498                                                  mouseEvent.button, mouseEvent.type,
499                                                  keyModifiers, source);
500     }
501     m_previousCaptureWindow = hasCapture ? window : nullptr;
502     // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
503     // is sent for unhandled WM_XBUTTONDOWN.
504     return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
505         || QWindowSystemInterface::flushWindowSystemEvents();
506 }
507 
isValidWheelReceiver(QWindow * candidate)508 static bool isValidWheelReceiver(QWindow *candidate)
509 {
510     if (candidate) {
511         const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate);
512         if (toplevel->handle() && toplevel->handle()->isForeignWindow())
513             return true;
514         if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel))
515             return !ww->testFlag(QWindowsWindow::BlockedByModal);
516     }
517 
518     return false;
519 }
520 
redirectWheelEvent(QWindow * window,const QPoint & globalPos,int delta,Qt::Orientation orientation,Qt::KeyboardModifiers mods)521 static void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int delta,
522                                Qt::Orientation orientation, Qt::KeyboardModifiers mods)
523 {
524     // Redirect wheel event to one of the following, in order of preference:
525     // 1) The window under mouse
526     // 2) The window receiving the event
527     // If a window is blocked by modality, it can't get the event.
528 
529     QWindow *receiver = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE);
530     while (receiver && receiver->flags().testFlag(Qt::WindowTransparentForInput))
531         receiver = receiver->parent();
532     bool handleEvent = true;
533     if (!isValidWheelReceiver(receiver)) {
534         receiver = window;
535         if (!isValidWheelReceiver(receiver))
536             handleEvent = false;
537     }
538 
539     if (handleEvent) {
540         const QPoint point = (orientation == Qt::Vertical) ? QPoint(0, delta) : QPoint(delta, 0);
541         QWindowSystemInterface::handleWheelEvent(receiver,
542                                                  QWindowsGeometryHint::mapFromGlobal(receiver, globalPos),
543                                                  globalPos, QPoint(), point, mods);
544     }
545 }
546 
translateMouseWheelEvent(QWindow * window,HWND,MSG msg,LRESULT *)547 bool QWindowsMouseHandler::translateMouseWheelEvent(QWindow *window, HWND,
548                                                     MSG msg, LRESULT *)
549 {
550     const Qt::KeyboardModifiers mods = keyStateToModifiers(int(msg.wParam));
551 
552     int delta;
553     if (msg.message == WM_MOUSEWHEEL || msg.message == WM_MOUSEHWHEEL)
554         delta = GET_WHEEL_DELTA_WPARAM(msg.wParam);
555     else
556         delta = int(msg.wParam);
557 
558     Qt::Orientation orientation = (msg.message == WM_MOUSEHWHEEL
559                                   || (mods & Qt::AltModifier)) ?
560                                   Qt::Horizontal : Qt::Vertical;
561 
562     // according to the MSDN documentation on WM_MOUSEHWHEEL:
563     // a positive value indicates that the wheel was rotated to the right;
564     // a negative value indicates that the wheel was rotated to the left.
565     // Qt defines this value as the exact opposite, so we have to flip the value!
566     if (msg.message == WM_MOUSEHWHEEL)
567         delta = -delta;
568 
569     const QPoint globalPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
570     redirectWheelEvent(window, globalPos, delta, orientation, mods);
571 
572     return true;
573 }
574 
translateScrollEvent(QWindow * window,HWND,MSG msg,LRESULT *)575 bool QWindowsMouseHandler::translateScrollEvent(QWindow *window, HWND,
576                                                 MSG msg, LRESULT *)
577 {
578     // This is a workaround against some touchpads that send WM_HSCROLL instead of WM_MOUSEHWHEEL.
579     // We could also handle vertical scroll here but there's no reason to, there's no bug for vertical
580     // (broken vertical scroll would have been noticed long time ago), so lets keep the change small
581     // and minimize the chance for regressions.
582 
583     int delta = 0;
584     switch (LOWORD(msg.wParam)) {
585     case SB_LINELEFT:
586         delta = 120;
587         break;
588     case SB_LINERIGHT:
589         delta = -120;
590         break;
591     case SB_PAGELEFT:
592         delta = 240;
593         break;
594     case SB_PAGERIGHT:
595         delta = -240;
596         break;
597     default:
598         return false;
599     }
600 
601     redirectWheelEvent(window, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier);
602 
603     return true;
604 }
605 
606 // from bool QApplicationPrivate::translateTouchEvent()
translateTouchEvent(QWindow * window,HWND,QtWindows::WindowsEventType,MSG msg,LRESULT *)607 bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND,
608                                                QtWindows::WindowsEventType,
609                                                MSG msg, LRESULT *)
610 {
611     using QTouchPoint = QWindowSystemInterface::TouchPoint;
612     using QTouchPointList = QList<QWindowSystemInterface::TouchPoint>;
613 
614     if (!QWindowsContext::instance()->initTouch()) {
615         qWarning("Unable to initialize touch handling.");
616         return true;
617     }
618 
619     const QScreen *screen = window->screen();
620     if (!screen)
621         screen = QGuiApplication::primaryScreen();
622     if (!screen)
623         return true;
624     const QRect screenGeometry = screen->geometry();
625 
626     const int winTouchPointCount = int(msg.wParam);
627     QScopedArrayPointer<TOUCHINPUT> winTouchInputs(new TOUCHINPUT[winTouchPointCount]);
628     memset(winTouchInputs.data(), 0, sizeof(TOUCHINPUT) * size_t(winTouchPointCount));
629 
630     QTouchPointList touchPoints;
631     touchPoints.reserve(winTouchPointCount);
632     Qt::TouchPointStates allStates;
633 
634     GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(msg.lParam),
635                       UINT(msg.wParam), winTouchInputs.data(), sizeof(TOUCHINPUT));
636     for (int i = 0; i < winTouchPointCount; ++i) {
637         const TOUCHINPUT &winTouchInput = winTouchInputs[i];
638         int id = m_touchInputIDToTouchPointID.value(winTouchInput.dwID, -1);
639         if (id == -1) {
640             id = m_touchInputIDToTouchPointID.size();
641             m_touchInputIDToTouchPointID.insert(winTouchInput.dwID, id);
642         }
643         QTouchPoint touchPoint;
644         touchPoint.pressure = 1.0;
645         touchPoint.id = id;
646         if (m_lastTouchPositions.contains(id))
647             touchPoint.normalPosition = m_lastTouchPositions.value(id);
648 
649         const QPointF screenPos = QPointF(winTouchInput.x, winTouchInput.y) / qreal(100.);
650         if (winTouchInput.dwMask & TOUCHINPUTMASKF_CONTACTAREA)
651             touchPoint.area.setSize(QSizeF(winTouchInput.cxContact, winTouchInput.cyContact) / qreal(100.));
652         touchPoint.area.moveCenter(screenPos);
653         QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(),
654                                          screenPos.y() / screenGeometry.height());
655         const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
656         touchPoint.normalPosition = normalPosition;
657 
658         if (winTouchInput.dwFlags & TOUCHEVENTF_DOWN) {
659             touchPoint.state = Qt::TouchPointPressed;
660             m_lastTouchPositions.insert(id, touchPoint.normalPosition);
661         } else if (winTouchInput.dwFlags & TOUCHEVENTF_UP) {
662             touchPoint.state = Qt::TouchPointReleased;
663             m_lastTouchPositions.remove(id);
664         } else {
665             touchPoint.state = (stationaryTouchPoint
666                      ? Qt::TouchPointStationary
667                      : Qt::TouchPointMoved);
668             m_lastTouchPositions.insert(id, touchPoint.normalPosition);
669         }
670 
671         allStates |= touchPoint.state;
672 
673         touchPoints.append(touchPoint);
674     }
675 
676     CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(msg.lParam));
677 
678     // all touch points released, forget the ids we've seen, they may not be reused
679     if (allStates == Qt::TouchPointReleased)
680         m_touchInputIDToTouchPointID.clear();
681 
682     QWindowSystemInterface::handleTouchEvent(window,
683                                              m_touchDevice,
684                                              touchPoints,
685                                              QWindowsKeyMapper::queryKeyboardModifiers());
686     return true;
687 }
688 
translateGestureEvent(QWindow * window,HWND hwnd,QtWindows::WindowsEventType,MSG msg,LRESULT *)689 bool QWindowsMouseHandler::translateGestureEvent(QWindow *window, HWND hwnd,
690                                                  QtWindows::WindowsEventType,
691                                                  MSG msg, LRESULT *)
692 {
693     Q_UNUSED(window)
694     Q_UNUSED(hwnd)
695     Q_UNUSED(msg)
696     return false;
697 }
698 
699 QT_END_NAMESPACE
700