Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Sources/CodexBar/CodexbarApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private var codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator?
private var cloudSyncCoordinator: CloudSyncCoordinator?
private var settingsWindowController: SettingsWindowController?
private lazy var placeholderSettingsWindowGuard = PlaceholderSettingsWindowGuard(
isKnownSettingsWindow: { [weak self] window in
self?.settingsWindowController?.window === window
})
private var hasInstalledLimitResetObservers = false
#if DEBUG
private var debugMemoryPressureObserver: NSObjectProtocol?
Expand Down Expand Up @@ -438,6 +442,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

func applicationWillFinishLaunching(_ notification: Notification) {
self.configureAppIconForMacOSVersion()
// The SwiftUI `Settings` scene is an empty placeholder; macOS otherwise presents it at launch.
self.placeholderSettingsWindowGuard.start()
}

func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
// CodexBar lives in the menu bar and has no untitled document to open at launch or on reopen.
false
}

func applicationDidFinishLaunching(_ notification: Notification) {
Expand Down
91 changes: 91 additions & 0 deletions Sources/CodexBar/PlaceholderSettingsWindowGuard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import AppKit
import CodexBarCore

/// CodexBar's preferences live in ``SettingsWindowController``. The SwiftUI `Settings` scene is an empty
/// placeholder that only carries the app-menu command group, and because it is the app's only scene macOS
/// presents it during launch, showing an empty "CodexBar Settings" window (#3053).
enum PlaceholderSettingsWindowDecision {
/// SwiftUI names the window of a `Settings` scene with this fragment (identifier and frame autosave name).
static let swiftUISettingsNameFragment = "com_apple_SwiftUI_Settings"

/// A window CodexBar never wants onscreen: SwiftUI's placeholder Settings window.
static func shouldClose(identifier: String?, frameAutosaveName: String, isKnownSettingsWindow: Bool) -> Bool {
guard !isKnownSettingsWindow, identifier != SettingsWindowIdentity.identifier else { return false }
if let identifier, identifier.contains(self.swiftUISettingsNameFragment) { return true }
return frameAutosaveName.contains(self.swiftUISettingsNameFragment)
}
}

/// Closes the empty SwiftUI `Settings` placeholder window whenever macOS presents it, so the AppKit
/// Settings window stays CodexBar's only preferences surface.
@MainActor
final class PlaceholderSettingsWindowGuard {
typealias WindowsProvider = @MainActor () -> [NSWindow]
typealias WindowPredicate = @MainActor (NSWindow) -> Bool
typealias WindowAction = @MainActor (NSWindow) -> Void

private let windows: WindowsProvider
private let isKnownSettingsWindow: WindowPredicate
private let closeWindow: WindowAction
private let logger = CodexBarLog.logger(LogCategories.app)
private var isStarted = false

init(
windows: @escaping WindowsProvider = { NSApp?.windows ?? [] },
isKnownSettingsWindow: @escaping WindowPredicate = { _ in false },
closeWindow: @escaping WindowAction = { $0.close() })
{
self.windows = windows
self.isKnownSettingsWindow = isKnownSettingsWindow
self.closeWindow = closeWindow
}

/// Sweeps once and keeps sweeping as window state changes; SwiftUI presents the placeholder after
/// `applicationWillFinishLaunching`, and window restoration can bring it back later.
func start() {
guard !self.isStarted else { return }
self.isStarted = true
let center = NotificationCenter.default
for name in [
NSApplication.didFinishLaunchingNotification,
NSWindow.didBecomeKeyNotification,
NSWindow.didBecomeMainNotification,
NSWindow.didUpdateNotification,
] {
center.addObserver(
self,
selector: #selector(self.windowStateDidChange(_:)),
name: name,
object: nil)
}
self.sweep()
}

@discardableResult
func sweep() -> Int {
var closedCount = 0
for window in self.windows() {
guard PlaceholderSettingsWindowDecision.shouldClose(
identifier: window.identifier?.rawValue,
frameAutosaveName: window.frameAutosaveName,
isKnownSettingsWindow: self.isKnownSettingsWindow(window))
else { continue }
self.closeWindow(window)
closedCount += 1
}
if closedCount > 0 {
self.logger.info(
"Closed placeholder SwiftUI Settings window",
metadata: ["count": "\(closedCount)"])
}
return closedCount
}

@objc private func windowStateDidChange(_: Notification) {
self.sweep()
}

deinit {
NotificationCenter.default.removeObserver(self)
}
}
8 changes: 8 additions & 0 deletions Tests/CodexBarTests/AppDelegateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import Testing

@MainActor
struct AppDelegateTests {
@Test
func `refuses the untitled window macOS would fill with the empty Settings scene`() {
_ = NSApplication.shared
let appDelegate = AppDelegate()

#expect(appDelegate.applicationShouldOpenUntitledFile(NSApplication.shared) == false)
}

@Test
func `builds status controller after launch`() {
let appDelegate = AppDelegate()
Expand Down
80 changes: 80 additions & 0 deletions Tests/CodexBarTests/PlaceholderSettingsWindowGuardTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import AppKit
import Testing
@testable import CodexBar

@MainActor
struct PlaceholderSettingsWindowGuardTests {
@Test
func `closes the empty SwiftUI Settings placeholder window`() {
_ = NSApplication.shared
let placeholder = self.makeWindow(
identifier: "com_apple_SwiftUI_Settings_window",
frameAutosaveName: "com_apple_SwiftUI_Settings_window")
var closed: [NSWindow] = []
let guardian = PlaceholderSettingsWindowGuard(
windows: { [placeholder] },
isKnownSettingsWindow: { _ in false },
closeWindow: { closed.append($0) })

#expect(guardian.sweep() == 1)
#expect(closed.count == 1)
#expect(closed.first === placeholder)
}

@Test
func `keeps the AppKit Settings window and unrelated windows onscreen`() {
_ = NSApplication.shared
let settingsWindow = self.makeWindow(identifier: SettingsWindowIdentity.identifier)
let updateWindow = self.makeWindow(identifier: "SUUpdateAlert")
var closed: [NSWindow] = []
let guardian = PlaceholderSettingsWindowGuard(
windows: { [settingsWindow, updateWindow] },
isKnownSettingsWindow: { $0 === settingsWindow },
closeWindow: { closed.append($0) })

#expect(guardian.sweep() == 0)
#expect(closed.isEmpty)
}

@Test
func `recognizes the placeholder by frame autosave name when the identifier is missing`() {
#expect(PlaceholderSettingsWindowDecision.shouldClose(
identifier: nil,
frameAutosaveName: "com_apple_SwiftUI_Settings_window",
isKnownSettingsWindow: false))
}

@Test
func `never closes the registered Settings window`() {
#expect(!PlaceholderSettingsWindowDecision.shouldClose(
identifier: "com_apple_SwiftUI_Settings_window",
frameAutosaveName: "com_apple_SwiftUI_Settings_window",
isKnownSettingsWindow: true))
#expect(!PlaceholderSettingsWindowDecision.shouldClose(
identifier: SettingsWindowIdentity.identifier,
frameAutosaveName: SettingsWindowIdentity.frameAutosaveName,
isKnownSettingsWindow: false))
}

@Test
func `leaves windows without SwiftUI Settings naming alone`() {
#expect(!PlaceholderSettingsWindowDecision.shouldClose(
identifier: nil,
frameAutosaveName: "",
isKnownSettingsWindow: false))
}

private func makeWindow(identifier: String, frameAutosaveName: String = "") -> NSWindow {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 100, height: 100),
styleMask: [.titled, .closable],
backing: .buffered,
defer: true)
window.identifier = NSUserInterfaceItemIdentifier(identifier)
if !frameAutosaveName.isEmpty {
window.setFrameAutosaveName(frameAutosaveName)
}
window.isReleasedWhenClosed = false
return window
}
}