summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2021-09-07 15:29:31 +0200
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2021-09-23 03:20:08 +0000
commit8a8be2d638ac6e54cab359e6ac13f73f53787481 (patch)
tree5ea566aeba308e343bb993131fb058481a4f7910 /src/plugins/platforms
parent8f13af5d7b9b659208a8a93e6581d30b434dae1f (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.mm16
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm9
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm103
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)