diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 1cc1f9fc..711d6c9f 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -3695,14 +3695,14 @@ enum AppIconSettings { static func applyIcon(_ mode: AppIconMode) { switch mode { case .automatic: - // Let the asset catalog handle appearance-based icon selection (macOS 15+). - // Reset to the default bundle icon. - NSApplication.shared.applicationIconImage = nil + AppIconAppearanceObserver.shared.startObserving() case .light: + AppIconAppearanceObserver.shared.stopObserving() if let icon = NSImage(named: "AppIconLight") { NSApplication.shared.applicationIconImage = icon } case .dark: + AppIconAppearanceObserver.shared.stopObserving() if let icon = NSImage(named: "AppIconDark") { NSApplication.shared.applicationIconImage = icon } @@ -3710,6 +3710,37 @@ enum AppIconSettings { } } +final class AppIconAppearanceObserver: NSObject { + static let shared = AppIconAppearanceObserver() + private var observation: NSKeyValueObservation? + + private override init() { super.init() } + + func startObserving() { + applyIconForCurrentAppearance() + guard observation == nil else { return } + observation = NSApp.observe(\.effectiveAppearance, options: []) { [weak self] _, _ in + DispatchQueue.main.async { + guard let self, self.observation != nil else { return } + self.applyIconForCurrentAppearance() + } + } + } + + func stopObserving() { + observation?.invalidate() + observation = nil + } + + private func applyIconForCurrentAppearance() { + let isDark = NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + let imageName = isDark ? "AppIconDark" : "AppIconLight" + if let icon = NSImage(named: imageName) { + NSApplication.shared.applicationIconImage = icon + } + } +} + enum QuitWarningSettings { static let warnBeforeQuitKey = "warnBeforeQuitShortcut" static let defaultWarnBeforeQuit = true