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