Skip to content

Keep the configure sheet alive between reads - #5

Merged
winebarrel merged 1 commit into
mainfrom
fix-configure-sheet-lifetime
Aug 10, 2026
Merged

winebarrel merged 1 commit into
mainfrom
fix-configure-sheet-lifetime

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

Pressing Options sometimes did nothing.

Cause

configureSheet built a fresh ConfigureSheetController on every read and stored
it in the view's only slot:

override var configureSheet: NSWindow? {
    let controller = ConfigureSheetController { … }   // new every read
    sheetController = controller                       // drops the previous one
    return controller.window
}

The window's only owner was that slot, so the second read deallocated the window
handed out by the first. Two ways that bites:

  • a host that reads the property twice — once to decide, once to present — is left
    presenting a deallocated window
  • a host that replaces the ScreenSaverView between reading the property and
    presenting it loses the window with the old view. The log shows exactly that
    happening around display reconfiguration:
CGSDisplayNotifyProc: got notification kCGSDisplayWillReconfigure
-[ScreenSaverView setScreenSaverModule:] -- Setting module “NecoSaver” for view: 0xbf5451180

Either way the sheet never appears, and no crash report is filed — the window is
simply gone by the time it is wanted. That matches the "sometimes" in the report.

Separately, the window was left with isReleasedWhenClosed at its default of
true while ARC owned it through the controller: an over-release waiting for a
host that closes the sheet rather than ordering it out.

Fix

One controller, built on first use and kept. reload() re-reads the stored
settings each time the sheet is handed over, so reopening shows what is stored
rather than where the last visit was left. isReleasedWhenClosed turned off.

Verified

Driving the installed bundle the way a host does, before and after:

                              before                    after
read #1                       NSWindow 0x…0c80          NSWindow 0x…8c80
read #2                       NSWindow 0x…1900          NSWindow 0x…8c80
window from read #1           DEALLOCATED               alive
isReleasedWhenClosed          true                      false

Reuse does not leak edits — flip a checkbox, cancel, reopen:

open 1: Paw prints = on
open 1: flipped to off
same window reused: true
open 2: Paw prints = on (stored value — cancel was honoured)

Three present/dismiss rounds stay clean (attachedSheet back to nil each time).
Both schemes build and analyse; swiftlint --strict, swiftlint analyze --strict
and swiftformat --lint are clean.

What this does not prove is that the original symptom is gone — it was
intermittent, and the harness can only show that the mechanism behind it is. That
needs real use in System Settings.

Elsewhere

winebarrel/Macstify has the same
configureSheet shape and the same latent bug.

Pressing Options sometimes did nothing. `configureSheet` built a fresh
ConfigureSheetController on every read and stored it in the view's only slot, so
the second read dropped the last owner of the window handed out by the first.
A host that reads the property twice — or that replaces the ScreenSaverView
between reading it and presenting it, which happens on display reconfiguration —
was left presenting a deallocated window, and nothing appeared.

Exercising the installed bundle the way a host does confirms it: reading the
property twice reported the first window DEALLOCATED, and now reports the same
pointer, alive.

The controller is now built once and kept, and reload() re-reads the stored
settings each time the sheet is handed over, so reopening shows what is stored
rather than where the last visit was left. Verified by flipping a checkbox,
cancelling, and reopening: the control comes back at its stored value.

The window also had isReleasedWhenClosed left at its default of true while ARC
owned it through the controller, which is an over-release waiting for a host that
closes the sheet rather than ordering it out. Turned off.
@winebarrel
winebarrel enabled auto-merge August 10, 2026 03:45
@winebarrel
winebarrel merged commit 2a679c6 into main Aug 10, 2026
3 checks passed
@winebarrel
winebarrel deleted the fix-configure-sheet-lifetime branch August 10, 2026 03:46
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.

1 participant