Skip to content

Fix Minimal Mode reverting on macOS when entered while maximized (#2365)#2367

Merged
ten9876 merged 1 commit intomainfrom
fix/minimal-mode-macos-revert-2365
May 5, 2026
Merged

Fix Minimal Mode reverting on macOS when entered while maximized (#2365)#2367
ten9876 merged 1 commit intomainfrom
fix/minimal-mode-macos-revert-2365

Conversation

@aethersdr-agent
Copy link
Copy Markdown
Contributor

Fixes #2365.

Summary

On macOS, activating Minimal Mode from a maximized/fullscreen window made the window briefly collapse to applet width and immediately snap back to its full-size geometry. The View → Minimal Mode menu item, Ctrl+M, and the title-bar minimal-mode button all exhibited the flash-then-revert behavior.

Root cause

MainWindow::toggleMinimalMode(true) calls setFixedWidth(260) on the top-level window. On Windows/Linux this synchronously clears Qt::WindowMaximized/Qt::WindowFullScreen before the function returns. On macOS, AppKit delivers the WindowStateChange notification asynchronously, so the Maximized bit survives into the deferred event.

When MainWindow::changeEvent then fires with m_minimalMode == true and the Maximized bit still set, it schedules a toggleMinimalMode(false) because there is no entering-side re-entry guard symmetric to m_exitingMinimalMode. That handler restores FullModeGeometry (which encoded the original maximized rect), so the window snaps back.

This regressed in #2299 (commit 0270b78), which collapsed the multiple minimal-exit paths into the single changeEvent branch and exposed the missing entering-side guard.

Fix

Two small, localized changes (src/gui/MainWindow.cpp, src/gui/MainWindow.h):

  1. Drop the Maximized/FullScreen bit at the top of the enter path. In toggleMinimalMode(true), after saveGeometry() is captured into FullModeGeometry, call showNormal() if the window is currently maximized or fullscreen. FullModeGeometry preserves the maximized rect, so exit-minimal still restores the user's pre-minimal window.
  2. Add a symmetric m_enteringMinimalMode guard alongside m_exitingMinimalMode. Set at the start of toggleMinimalMode(true), cleared via QTimer::singleShot(0) so any AppKit-deferred WindowStateChange queued during the enter path drains through changeEvent's early-return before the guard drops. changeEvent honors the new flag at its early-return.

Defense-in-depth — either change alone would resolve the symptom; the combination guards against any further AppKit-deferred notifications between setFixedWidth(260) and the function returning.

Risk

Low — additive change (one extra showNormal() + one boolean flag). On Windows/Linux the windowState() & (Maximized|FullScreen) check is false at entry (synchronous state clear), so the new code path is a no-op.

Test plan

  • macOS: Maximize the window → Ctrl+M → window stays in narrow minimal layout (does not revert).
  • macOS: Same via View → Minimal Mode menu item.
  • macOS: Same via title-bar minimal-mode button.
  • macOS: From minimal, exit via Ctrl+M / double-click title bar / maximize button → returns to the user's pre-minimal window.
  • macOS: Enter Minimal Mode from a non-maximized window (regression check) → behaves as before.
  • Windows / Linux: Enter/exit Minimal Mode from maximized and normal states (regression check).

Generated by AetherClaude (automated agent for AetherSDR)

On macOS, AppKit delivers Qt::WindowStateChange asynchronously, so the
Maximized/FullScreen bit could survive setFixedWidth(260) inside
toggleMinimalMode(true). The deferred WindowStateChange then arrived
with m_minimalMode==true and the Maximized bit still set, causing
changeEvent to schedule a spurious toggleMinimalMode(false) — the
window collapsed for a frame and snapped back to the full-size geometry
restored from FullModeGeometry.

- toggleMinimalMode(true): drop maximized/fullscreen state via
  showNormal() before forcing the applet width. FullModeGeometry is
  saved first, so exit can still restore the user's pre-minimal window.
- Add m_enteringMinimalMode guard symmetric to m_exitingMinimalMode and
  honor it in changeEvent. Cleared via singleShot(0) so any AppKit-
  deferred WindowStateChange queued during the enter path drains through
  the early-return before the guard drops.

Both behaviors are no-ops on Windows/Linux where setFixedWidth on a
maximized top-level synchronously clears the state bit.

Regressed in #2299, which collapsed minimal-exit paths into the single
changeEvent branch and exposed the missing entering-side guard.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@aethersdr-agent aethersdr-agent Bot requested a review from ten9876 as a code owner May 5, 2026 02:05
@aethersdr-agent aethersdr-agent Bot enabled auto-merge (squash) May 5, 2026 02:05
Copy link
Copy Markdown
Contributor Author

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — thanks for the careful diagnosis and writeup.

The fix is well-targeted and the asymmetry analysis matches what I see in the code:

  • MainWindow.cpp:4201 — adding m_enteringMinimalMode to changeEvent's early-return is the natural symmetric counterpart to the existing m_exitingMinimalMode guard at MainWindow.cpp:4213. The ordering is right too: !m_minimalMode short-circuits ahead of the new flag, so the exit path doesn't have to clear it.
  • MainWindow.cpp:9863showNormal() runs after saveGeometry() is captured into FullModeGeometry, so the exit path still restores the user's pre-minimal (maximized) window. Confirmed by the existing exit-side handling at MainWindow.cpp:9903-9908 which independently checks the maximized bit on exit.
  • The deferred QTimer::singleShot(0, ...) to clear the guard mirrors the existing m_exitingMinimalMode clear pattern and gives any AppKit-deferred WindowStateChange a chance to drain through changeEvent's early-return.

On Windows/Linux the new windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen) check is effectively dead code on entry (synchronous state clearing), so the regression check on those platforms should be straightforward.

Minor non-blocking observation: there's a theoretical race if the user toggles minimal mode rapidly enough that a second enter happens before the first deferred clear fires — the first timer would clear the flag while the second enter is still in flight. This is unlikely in practice (it requires sub-event-loop human input timing) and the showNormal() defense-in-depth covers most of the surface anyway, so I don't think it needs addressing here.

Nice fix.

@ten9876 ten9876 merged commit a253231 into main May 5, 2026
5 checks passed
@ten9876 ten9876 deleted the fix/minimal-mode-macos-revert-2365 branch May 5, 2026 05:52
@ten9876
Copy link
Copy Markdown
Owner

ten9876 commented May 5, 2026

Claude here on Jeremy's behalf — merged via admin squash. Stale-code audit clean, root-cause precise (correctly identifies #2299's regression source), defense-in-depth fix is the right shape. Jeremy will smoke-test on macOS and follow up here if anything surfaces.

73, Jeremy KK7GWY & Claude (AI dev partner)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Minimal mode not working

2 participants