summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoawindow.mm
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2022-08-18 21:04:36 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2022-08-22 20:10:13 +0200
commit5afb04d79ba246d9dca30c666511ce7505113ac5 (patch)
treeedee7cd916400c43b9bc123e3d6f3a94c1dbdb03 /src/plugins/platforms/cocoa/qcocoawindow.mm
parent03144190dfa329444d8941781d20265dfe7ce59c (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.mm73
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();
}
}