diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2021-09-07 15:29:31 +0200 |
---|---|---|
committer | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2021-09-23 03:20:08 +0000 |
commit | 8a8be2d638ac6e54cab359e6ac13f73f53787481 (patch) | |
tree | 5ea566aeba308e343bb993131fb058481a4f7910 /src/plugins/platforms | |
parent | 8f13af5d7b9b659208a8a93e6581d30b434dae1f (diff) |
macOS: Change NSTrackingArea to use NSTrackingActiveAlways
The current implementation configured NSTrackingArea to be active
while the application was active (NSTrackingActiveInActiveApp).
But because of a bug in AppKit, the tracking area would sometimes
stop being updated, with the result that no mouse events was being
sent from AppKit. One way to trigger this bug would be to
deactivate the app using cmd+tab (fast enough so that the task
switcher is not showing), and reactivate it again using a
mouse click.
To work around this issue, this patch will instead configure
the tracking area to always listen for tracking events, even when
the application is inactive (NSTrackingActiveAlways). By doing so,
we bypass the apparently broken behavior in AppKit related to
activation. The downside is that we then also get tracking events
while the application is inactive, which is against how Qt should
work. So we therefore need to now check if the application is active
before we forward any tracking events to QWSI.
With NSTrackingActiveAlways we no longer get enter/leave events
from AppKit when the application is activated/deactivated and the
mouse stays on top of the application, so we now also need to handle
this explicitly from the application delegate. Since we already need
to keep track of which QWindow is under the mouse to be able to send
out enter/leave from mouse moves, we use the same variable
(s_windowUnderMouse) for this purpose as well.
Because of QTBUG-35109, there was already a code path in QCocoaWindow
that sent out an enter event when a window became key. This is no
longer needed now that we send out enter/leave when the application is
activated/deactivated. It's also questionable if sending an enter
event based on a window's key status is correct, since we in Qt should
also send out enter events for inactive windows. So we remove this code
path since it interferes with (both the old and) the new implementation.
Fixes: QTBUG-36926
Task-number: QTBUG-94447
Change-Id: I5c1105bc3102925c9c65964b4a7d1e02efd01735
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm | 16 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 9 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_mouse.mm | 103 |
4 files changed, 94 insertions, 38 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 40d8f639fc..c32b8d9c30 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -269,6 +269,16 @@ QT_USE_NAMESPACE [reflectionDelegate applicationDidBecomeActive:notification]; QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); + + if (QCocoaWindow::s_windowUnderMouse) { + QPointF windowPoint; + QPointF screenPoint; + QNSView *view = qnsview_cast(QCocoaWindow::s_windowUnderMouse->m_view); + [view convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window(); + qCInfo(lcQpaMouse) << "Application activated with mouse at" << windowPoint << "; sending" << QEvent::Enter << "to" << windowUnderMouse; + QWindowSystemInterface::handleEnterEvent(windowUnderMouse, windowPoint, screenPoint); + } } - (void)applicationDidResignActive:(NSNotification *)notification @@ -277,6 +287,12 @@ QT_USE_NAMESPACE [reflectionDelegate applicationDidResignActive:notification]; QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); + + if (QCocoaWindow::s_windowUnderMouse) { + QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window(); + qCInfo(lcQpaMouse) << "Application deactivated; sending" << QEvent::Leave << "to" << windowUnderMouse; + QWindowSystemInterface::handleLeaveEvent(windowUnderMouse); + } } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index fbfe20fe07..e097a69c4d 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -248,8 +248,8 @@ public: // for QNSView Qt::WindowStates m_lastReportedWindowState; Qt::WindowModality m_windowModality; - QPointer<QWindow> m_enterLeaveTargetWindow; - bool m_windowUnderMouse; + + static QPointer<QCocoaWindow> s_windowUnderMouse; bool m_initialized; bool m_inSetVisible; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index a1cc8052f1..f5f7ae6bff 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -132,6 +132,7 @@ static void qRegisterNotificationCallbacks() Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks) const int QCocoaWindow::NoAlertRequest = -1; +QPointer<QCocoaWindow> QCocoaWindow::s_windowUnderMouse; QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win) @@ -139,7 +140,6 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_nsWindow(nil) , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) - , m_windowUnderMouse(false) , m_initialized(false) , m_inSetVisible(false) , m_inSetGeometry(false) @@ -1181,13 +1181,6 @@ void QCocoaWindow::windowDidBecomeKey() if (isForeignWindow()) return; - if (m_windowUnderMouse) { - QPointF windowPoint; - QPointF screenPoint; - [qnsview_cast(m_view) convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleEnterEvent(m_enterLeaveTargetWindow, windowPoint, screenPoint); - } - QNSView *firstResponderView = qt_objc_cast<QNSView *>(m_view.window.firstResponder); if (!firstResponderView) return; diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index d70d87e40d..1040b66644 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -278,8 +278,13 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) m_mouseMoveHelper = [[QNSViewMouseMoveHelper alloc] initWithView:self]; - NSUInteger trackingOptions = NSTrackingActiveInActiveApp - | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate; + NSUInteger trackingOptions = NSTrackingCursorUpdate | NSTrackingMouseEnteredAndExited; + + // Ideally we should have used NSTrackingActiveInActiveApp, but that + // fails when the application is deactivated from using e.g cmd+tab, and later + // reactivated again from a mouse click. So as a work-around we use NSTrackingActiveAlways + // instead, and simply ignore any related callbacks while the application is inactive. + trackingOptions |= NSTrackingActiveAlways; // Ideally, NSTrackingMouseMoved should be turned on only if QWidget::mouseTracking // is enabled, hover is on, or a tool tip is set. Unfortunately, Qt will send "tooltip" @@ -499,6 +504,9 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) // uses the legacy cursorRect API, so the cursor is reset to the arrow // cursor. See rdar://34183708 + if (!NSApp.active) + return; + auto previousCursor = NSCursor.currentCursor; if (self.cursor) @@ -515,29 +523,42 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) if (!m_platformWindow) return; - if ([self isTransparentForUserInput]) - return; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); - // Top-level windows generate enter-leave events for sub-windows. // Qt wants to know which window (if any) will be entered at the // the time of the leave. This is dificult to accomplish by // handling mouseEnter and mouseLeave envents, since they are sent // individually to different views. - if (m_platformWindow->isContentView() && childWindow) { - if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { - QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); - m_platformWindow->m_enterLeaveTargetWindow = childWindow; + QPointF windowPoint; + QPointF screenPoint; + QCocoaWindow *windowToLeave = nullptr; + + if (m_platformWindow->isContentView()) { + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindow *childUnderMouse = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QCocoaWindow *childWindow = static_cast<QCocoaWindow *>(childUnderMouse->handle()); + if (childWindow != QCocoaWindow::s_windowUnderMouse) { + if (QCocoaWindow::s_windowUnderMouse) + windowToLeave = QCocoaWindow::s_windowUnderMouse; + QCocoaWindow::s_windowUnderMouse = childWindow; } } + if (!NSApp.active) + return; + + if ([self isTransparentForUserInput]) + return; + + if (windowToLeave) { + qCInfo(lcQpaMouse) << "Detected new window under mouse at" << windowPoint << "; sending" + << QEvent::Enter << QCocoaWindow::s_windowUnderMouse->window() + << QEvent::Leave << windowToLeave->window(); + QWindowSystemInterface::handleEnterLeaveEvent(QCocoaWindow::s_windowUnderMouse->window(), windowToLeave->window(), windowPoint, screenPoint); + } + // Cocoa keeps firing mouse move events for obscured parent views. Qt should not // send those events so filter them out here. - if (childWindow != m_platformWindow->window()) + if (m_platformWindow != QCocoaWindow::s_windowUnderMouse) return; [self handleMouseEvent: theEvent]; @@ -549,10 +570,22 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) if (!m_platformWindow) return; - m_platformWindow->m_windowUnderMouse = true; - - if ([self isTransparentForUserInput]) - return; + // We send out enter and leave events mainly from mouse move events (mouseMovedImpl). + // Therefore, in most cases, we should not send out enter/leave events from here, as + // this results in duplicated enter/leave events being delivered. + // This is especially important when working with NSTrackingArea, since AppKit documents that + // the order of enter/exit events when several NSTrackingAreas are in use is not guaranteed. + // So if we just forwarded enter/leave events from NSTrackingArea directly, it would not only + // result in duplicated events, but also sometimes events that would be out of sync. + // But not all enter events can be resolved from mouse move events. E.g if a window is raised + // in front of the mouse, or if the application is activated while the mouse is on top of a + // window, we need to send out enter events for those cases as well. And we do so from this + // function to support the former case. But only when we receive an enter event for the + // top-level window, when no child QWindows are being hovered from before. + // Since QWSI expects us to send both the window entered, and the window left, in the same + // callback, we manually keep track of which child QWindow is under the mouse at any point + // in time (s_windowUnderMouse). The latter is also used to also send out enter/leave + // events when the application is activated/deactivated. // Top-level windows generate enter events for sub-windows. if (!m_platformWindow->isContentView()) @@ -561,10 +594,18 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QWindow *childUnderMouse = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QCocoaWindow::s_windowUnderMouse = static_cast<QCocoaWindow *>(childUnderMouse->handle()); + + if ([self isTransparentForUserInput]) + return; + + if (!NSApp.active) + return; - qCInfo(lcQpaMouse) << QEvent::Enter << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons(); - QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); + qCInfo(lcQpaMouse) << "Mouse entered" << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons() + << "; sending" << QEvent::Enter << "to" << QCocoaWindow::s_windowUnderMouse->window(); + QWindowSystemInterface::handleEnterEvent(QCocoaWindow::s_windowUnderMouse->window(), windowPoint, screenPoint); } - (void)mouseExitedImpl:(NSEvent *)theEvent @@ -573,18 +614,24 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) if (!m_platformWindow) return; - m_platformWindow->m_windowUnderMouse = false; + // Top-level windows generate leave events for sub-windows. + if (!m_platformWindow->isContentView()) + return; + + QCocoaWindow *windowToLeave = QCocoaWindow::s_windowUnderMouse; + QCocoaWindow::s_windowUnderMouse = nullptr; if ([self isTransparentForUserInput]) return; - // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->isContentView()) + if (!NSApp.active) + return; + + if (!windowToLeave) return; - qCInfo(lcQpaMouse) << QEvent::Leave << self; - QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); - m_platformWindow->m_enterLeaveTargetWindow = 0; + qCInfo(lcQpaMouse) << "Mouse left" << self << "; sending" << QEvent::Leave << "to" << windowToLeave->window(); + QWindowSystemInterface::handleLeaveEvent(windowToLeave->window()); } #if QT_CONFIG(wheelevent) |