From ed1def47a2b26360669e2a6dd7c6e354955492e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 20 Oct 2023 19:08:32 +0200 Subject: [PATCH] macOS: Don't refuse key window for direct transient parent of modal window As explain in detail in 1bde2036051b3aaf5896ef1ff50c08c11ea1eba9, AppKit doesn't treat non-direct transient parents of a window modal window as being blocked by that modal window, but Qt does. To align with Qt, we worked around it by returning NO from canBecomeKeyWindow for windows that were blocked by a modal window. This however had an unintended side effect for native dialogs, due to the way we hide these dialogs. When a native dialog is closed, AppKit will look for another window to make key, and as part of that it checks canBecomeKeyWindow. The problem is that the modal blocked status has not been updated in QGuiApplicationPrivate at that point, so we tell AppKit it can't make the transient parent key. The modal blocked status is only updated once we hit QWidget::setVisible, at which point dialogs like QMessageBox and QColorDialog has already called setNativeDialogVisible(false) in its setVisible() override, triggering the close logic described above. To fix this properly we need to have the dialogs call the base class first when hiding, and then setNativeDialogVisible(), and also have the macOS native dialog helpers react to hide() by ordering out the window in case it didn't work at an earlier point. This kind of change is risky for Qt 6.5/6.6 however, so we opt for a simpler solution for now. By limiting the original workaround for non-direct transient parents not being blocked to just those transient parents, and only for window modal dialogs, we avoid the issue with failing to transfer key status. Task-number: QTBUG-104905 Fixes: QTBUG-118320 Pick-to: 6.5 Change-Id: Iabbca0b74a7db4e9821a9b60730d01fbad1425db Reviewed-by: Timur Pocheptsov (cherry picked from commit 4a16e51bc3e248c6f82b70e176d605d54b9b2cad) Reviewed-by: Qt Cherry-pick Bot --- src/plugins/platforms/cocoa/qcocoawindow.mm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 2179a858409..7eb2a350d35 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1983,8 +1983,21 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder() if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput)) return true; - if (QWindowPrivate::get(window())->blockedByModalWindow) - return true; + // For application modal windows, as well as direct parent windows + // of window modal windows, AppKit takes care of blocking interaction. + // The Qt expectation however, is that all transient parents of a + // window modal window is blocked, as reflected by QGuiApplication. + // We reflect this by returning false from this function for transient + // parents blocked by a modal window, but limit it to the cases not + // covered by AppKit to avoid potential unwanted side effects. + QWindow *modalWindow = nullptr; + if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) { + if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) { + qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being" + << "blocked by" << modalWindow; + return true; + } + } if (m_inSetVisible) { QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");