macOS: Make QCocoaWindow::setVisible idempotent

The implementation of QCocoaWindow::setVisible is not idempotent,
as for modal windows it calls beginSheet/endSheet or starts/ends modal
sessions, which will confuse the AppKit modal session stack.

Once a window has been shown QWindowPrivate::setVisible hides/masks this
issue, by returning early if the visibility has not changed, but during
first show QWindowPrivate::setVisible will update the QWindow state,
before creating the platform window. If the platform window then picks
up the QWindow's visible state during creation, it will result in one
QPlatformWindow::setVisible() call during creation, and another one
via QWindowPrivate::setVisible after the platform window has been
created.

To fix this we can check the existing NSView.hidden state and skip
the setVisible call if it's already up to date. But one complication
is that our QCocoaWindow::setVisible has different behaviors depending
on whether the window has been initialized yet or not, due to how
handleGeometryChange skips sending geometry changes for uninitialized
windows. So we need to also bail out if we're still initializing the
window. This is fine, as we know we'll get a follow up setVisible
call via QWindow, both when QWindow itself creates the platform window
as part of setVisible, as well as when the user does a two step create
and then show.

For good measure we throw in a recursion check as well, in case some
of the logic in QCocoaWindow::setVisible before we update NSView.hidden
recurses back into QCocoaWindow::setVisible.

Pick-to: 6.5 6.6
Change-Id: Ibdcf4859e58d6684aac4490126d35eb12fdd5943
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-10-22 14:08:30 +02:00
parent 342b37f388
commit be268ae197

View File

@ -305,6 +305,31 @@ void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
// Our implementation of setVisible below is not idempotent, as for
// modal windows it calls beginSheet/endSheet or starts/ends modal
// sessions. However we can't simply guard for m_view.hidden already
// having the right state, as the behavior of this function differs
// based on whether the window has been initialized or not, as
// handleGeometryChange will bail out if the window is still
// initializing. Since we know we'll get a second setVisible
// call after creation, we can check for that case specifically,
// which means we can then safely guard on m_view.hidden changing.
if (!m_initialized) {
qCDebug(lcQpaWindow) << "Window still initializing, skipping setting visibility";
return; // We'll get another setVisible call after create is done
}
if (visible == !m_view.hidden) {
qCDebug(lcQpaWindow) << "No change in visible status. Ignoring.";
return;
}
if (m_inSetVisible) {
qCWarning(lcQpaWindow) << "Already setting window visible!";
return;
}
QScopedValueRollback<bool> rollback(m_inSetVisible, true);
QMacAutoReleasePool pool;