diff options
author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2022-08-18 21:04:36 +0200 |
---|---|---|
committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2022-08-22 20:10:13 +0200 |
commit | 5afb04d79ba246d9dca30c666511ce7505113ac5 (patch) | |
tree | edee7cd916400c43b9bc123e3d6f3a94c1dbdb03 /src/plugins/platforms/cocoa/qcocoawindow.mm | |
parent | 03144190dfa329444d8941781d20265dfe7ce59c (diff) |
Mac: close popups opened on inactive application on relevant user action
On macOS, users can right-click into an inactive application to open a
context menu without activating the application. Qt handles a number of
events to close open popups (window deactivating or a mouse press
outside the popup), but none of those will get called when the
application is already inactive. So the popup might stay open (and on
top of the window stack) when the user clicks into other applications,
or activates another window.
To fix this we need to watch for events outside of the Qt application on
which we need to close the popup: when the user presses a mouse button,
or activates another application using Cmd-Tab. But we don't want to
monitor for key events, as that requires user permission. Use a global
event monitor to watch for mouse presses, and an notification observer
to watch for application activations, and respond by closing all popups
(and removing the monitor and observer again).
Use the monitor as well to watch for mouse moves, and pass only those
events through the Qt event system so that mouse tracking in the menu
works even if the application is inactive. This change brings back a
version of the global event monitor we had in Qt 5.15.
However, a press into our own menu will trigger the activation observer
after the application became active, which would now close the menu. We
don't want that, so we also need to remove the observer when the
application becomes active (which makes sense anyway, as we will get
regular events from then on).
Pick-to: 6.4 6.3 6.2
Fixes: QTBUG-105474
Change-Id: I18573fcda09a46c27730bd670a795f4d467aab01
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 73 |
1 files changed, 72 insertions, 1 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index ef94b49736..ff8a15f0d6 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1226,7 +1226,7 @@ void QCocoaWindow::windowDidResignKey() // Make sure popups are closed before we deliver activation changes, which are // otherwise ignored by QApplication. - QGuiApplicationPrivate::instance()->closeAllPopups(); + closeAllPopups(); // The current key window will be non-nil if another window became key. If that // window is a Qt window, we delay the window activation event until the didBecomeKey @@ -1573,6 +1573,75 @@ void QCocoaWindow::requestActivateWindow() [m_view.window makeKeyWindow]; } +/* + Closes all popups, and removes observers and monitors. +*/ +void QCocoaWindow::closeAllPopups() +{ + QGuiApplicationPrivate::instance()->closeAllPopups(); + + removePopupMonitor(); +} + +void QCocoaWindow::removePopupMonitor() +{ + if (s_globalMouseMonitor) { + [NSEvent removeMonitor:s_globalMouseMonitor]; + s_globalMouseMonitor = nil; + } + if (s_applicationActivationObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver]; + s_applicationActivationObserver = nil; + } +} + +void QCocoaWindow::setupPopupMonitor() +{ + // we open a popup window while we are not active. None of our existing event + // handlers will get called if the user now clicks anywhere outside the application + // or activates another window. Use a global event monitor to watch for mouse + // presses, and close popups. We also want mouse tracking in the popup to work, so + // also watch for MouseMoved. + if (!s_globalMouseMonitor) { + // we only get LeftMouseDown events when we also set LeftMouseUp. + constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp + | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown + | NSEventMaskMouseMoved; + s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask + handler:^(NSEvent *e){ + if (!QGuiApplicationPrivate::instance()->popupActive()) { + removePopupMonitor(); + return; + } + const auto eventType = cocoaEvent2QtMouseEvent(e); + if (eventType == QEvent::MouseMove) { + if (s_windowUnderMouse) { + QWindow *window = s_windowUnderMouse->window(); + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint, + buttons, button, eventType); + } + } else { + closeAllPopups(); + } + }]; + } + // The activation observer also gets called when we become active because the user clicks + // into the popup. This should not close the popup, so QCocoaApplicationDelegate's + // applicationDidBecomeActive implementation removes this observer. + if (!s_applicationActivationObserver) { + s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter] + addObserverForName:NSWorkspaceDidActivateApplicationNotification + object:nil queue:nil + usingBlock:^(NSNotification *){ + closeAllPopups(); + }]; + } +} + QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) { QMacAutoReleasePool pool; @@ -1678,6 +1747,8 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) if ((type & Qt::Popup) == Qt::Popup) { nsWindow.hasShadow = YES; nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + if (QGuiApplication::applicationState() != Qt::ApplicationActive) + setupPopupMonitor(); } } |