diff --git a/README.md b/README.md index b7918fb..653f910 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,28 @@ cycle through the spectrum. | Color speed | 1.00× | 0.0–3.0 | Hue cycling rate. `0.00×` freezes each shape on its starting color. | Settings are stored per user through `ScreenSaverDefaults` under `jp.winebarrel.Macstify`. + +## Known issue: the Options button does nothing + +On macOS 26 the Options button stops responding, reliably if you switch the screen +saver away and back. Nothing appears, and nothing is logged. + +This is a bug in the host, not in the saver. System Settings spawns duplicate +`legacyScreenSaver` instances and loses track of them, so the configure sheet is +handed over, attached and ordered in — against a remote view window that is no +longer on screen. Traced from inside a screen saver bundle, the sheet reports +`isVisible == true` with a live `sheetParent`, while `occlusionState` never gains +`.visible`: attached to a ghost, and never drawn. Which window the host attaches +the sheet to is not something `configureSheet` can influence. + +Apple has it as FB19201567, along with FB19204084 for the `ScreenSaverView` +instances that pile up alongside; neither is fixed as of 26.1b3. See +[macOS 26 Tahoe Screen Saver issues](https://developer.apple.com/forums/thread/787444). + +Until it is fixed, restart the host: + +```sh +killall legacyScreenSaver +``` + +then quit System Settings and open it again. diff --git a/Sources/ConfigureSheetController.swift b/Sources/ConfigureSheetController.swift index a505735..2c41000 100644 --- a/Sources/ConfigureSheetController.swift +++ b/Sources/ConfigureSheetController.swift @@ -18,7 +18,7 @@ final class ConfigureSheetController: NSObject { let window: NSWindow - private var settings = MacstifySettings.standard + private var settings = MacstifySettings.load() private let onDismiss: (_ saved: Bool) -> Void private var rows: [Row] = [] @@ -33,17 +33,10 @@ final class ConfigureSheetController: NSObject { super.init() window.title = "Macstify" // ARC owns this window through `window`; leaving the default on would - // let a host that closes the sheet release it a second time. + // let a host that closes the sheet rather than ordering it out release + // it a second time. window.isReleasedWhenClosed = false buildInterface() - reload() - } - - /// Re-reads the saved settings into the controls. The same sheet is handed - /// to the host every time it asks, so this is what makes the second opening - /// show what is stored rather than where the first one was left. - func reload() { - settings = .load() refresh() } diff --git a/Sources/MacstifyView.swift b/Sources/MacstifyView.swift index b4027b8..e0db4ac 100644 --- a/Sources/MacstifyView.swift +++ b/Sources/MacstifyView.swift @@ -8,9 +8,8 @@ import ScreenSaver @objc(MacstifyView) final class MacstifyView: ScreenSaverView { private let engine: MacstifyEngine - /// Built once and kept: the host releases its reference once the sheet is - /// up, and a deallocated controller takes the sheet's window and targets - /// with it. + /// Held strongly: the host releases its reference once the sheet is up, + /// and a deallocated controller takes the sheet's targets with it. private var sheetController: ConfigureSheetController? override init?(frame: NSRect, isPreview: Bool) { @@ -55,25 +54,18 @@ final class MacstifyView: ScreenSaverView { true } - /// Hosts read this more than once, and can replace the view between reading - /// it and presenting it. Building a controller per read would hand back a - /// window whose only owner was dropped by the very next read — and the sheet - /// then simply never appears. One controller, kept, re-reading the settings - /// each time it is handed over. override var configureSheet: NSWindow? { - let controller = sheetController ?? makeSheetController() + let controller = ConfigureSheetController { [weak self] saved in + guard let self else { return } + if saved { + engine.apply(Self.settings(isPreview: isPreview)) + } + sheetController = nil + } sheetController = controller - controller.reload() return controller.window } - private func makeSheetController() -> ConfigureSheetController { - ConfigureSheetController { [weak self] saved in - guard let self, saved else { return } - engine.apply(Self.settings(isPreview: isPreview)) - } - } - private static func settings(isPreview: Bool) -> MacstifySettings { let settings = MacstifySettings.load() return isPreview ? settings.previewAdjusted : settings