import AppKitimport CodexBarCoreimport Observationimport QuartzCore // MARK: - Status item controller (AppKit-hosted icons, SwiftUI popovers) @MainActorprotocol StatusItemControlling: AnyObject { func openMenuFromShortcut() func runLoginFlowFromSettings(provider: UsageProvider) async func celebrationOriginPoint(for provider: UsageProvider?) -> CGPoint? func trimRebuildableCachesForMemoryPressure() -> MemoryPressureCacheTrimSummary #if DEBUG func seedRebuildableCachesForMemoryPressureProof() #endif func prepareForAppShutdown()} extension StatusItemControlling { func celebrationOriginPoint(for provider: UsageProvider?) -> CGPoint? { nil } func trimRebuildableCachesForMemoryPressure() -> MemoryPressureCacheTrimSummary { MemoryPressureCacheTrimSummary() } #if DEBUG func seedRebuildableCachesForMemoryPressureProof() {} #endif func prepareForAppShutdown() {}} @MainActorfinal class StatusItemController: NSObject, NSMenuDelegate, StatusItemControlling { // Disable SwiftUI menu cards + menu refresh work in tests to avoid swiftpm-testing-helper crashes. static var menuCardRenderingEnabled = !SettingsStore.isRunningTests private static let defaultMenuRefreshEnabled = !SettingsStore.isRunningTests private(set) static var menuRefreshEnabled = !SettingsStore.isRunningTests static let quotaWarningFlashDuration: TimeInterval = 60 private nonisolated static let statusItemAccessibilityTitle = "CodexBar" private nonisolated static let debugStatusItemAccessibilityTitle = "CodexBar Debug" private nonisolated static let statusItemAccessibilityIdentifierPrefix = "CodexBar.StatusItem" private nonisolated static let mergedLegacyDefaultItemIndex = 0 enum StatusItemIdentity { case merged case provider(UsageProvider) var autosaveName: String { switch self { case .merged: "codexbar-merged" case let .provider(provider): "codexbar-\(provider.rawValue)" } } var accessibilityIdentifier: String { switch self { case .merged: StatusItemController.statusItemAccessibilityIdentifierPrefix case let .provider(provider): "\(StatusItemController.statusItemAccessibilityIdentifierPrefix).\(provider.rawValue)" } } } nonisolated static func isDebugApp(bundleIdentifier: String?) -> Bool { bundleIdentifier?.contains(".debug") == true } nonisolated static func statusItemAccessibilityTitle(isDebugApp: Bool) -> String { isDebugApp ? self.debugStatusItemAccessibilityTitle : self.statusItemAccessibilityTitle } #if DEBUG var menuRefreshEnabledOverrideForTesting: Bool? #endif typealias Factory = @MainActor ( UsageStore, SettingsStore, AccountInfo, UpdaterProviding, PreferencesSelection, ManagedCodexAccountCoordinator, CodexAccountPromotionCoordinator) -> StatusItemControlling // swiftlint:disable:next function_parameter_count static func makeDefaultController( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, selection: PreferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator, codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator) -> StatusItemControlling { StatusItemController( store: store, settings: settings, account: account, updater: updater, preferencesSelection: selection, managedCodexAccountCoordinator: managedCodexAccountCoordinator, codexAccountPromotionCoordinator: codexAccountPromotionCoordinator) } static let defaultFactory: Factory = StatusItemController.makeDefaultController static var factory: Factory = StatusItemController.defaultFactory let store: UsageStore let settings: SettingsStore lazy var menuCardRefreshMonitor = MenuCardRefreshMonitor { [weak self] provider in self?.menuCardModel(for: provider) } let account: AccountInfo let updater: UpdaterProviding let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator let statusBar: NSStatusBar let menuCardRenderingEnabledForController: Bool let menuRefreshEnabledForController: Bool var statusItem: NSStatusItem var statusItems: [UsageProvider: NSStatusItem] = [:] var lastMenuProvider: UsageProvider? var menuProviders: [ObjectIdentifier: UsageProvider] = [:] var menuSession = MenuSessionCoordinator<ObjectIdentifier>() var menuReadinessSignatures: [ObjectIdentifier: String] = [:] let hostedSubviewRenderSignatures = NSMapTable<NSMenu, NSString>.weakToStrongObjects() /// Persistent Refresh rows are weakly tracked so their enabled state can change during menu tracking. let persistentRefreshItems = NSHashTable<NSMenuItem>.weakObjects() var menuCardHeightCache: [MenuCardHeightCacheKey: CGFloat] = [:] var measuredStandardMenuWidthCache: [String: CGFloat] = [:] var lastMenuAdjunctReadinessSignature = "" var lastMenuAdjunctReadinessBaselineVersion = 0 var rootOpenHandledMenuObservationSignature: String? var mergedMenu: NSMenu? var providerMenus: [UsageProvider: NSMenu] = [:] var fallbackMenu: NSMenu? var openMenus: [ObjectIdentifier: NSMenu] = [:] var menuRefreshTasks: [ObjectIdentifier: Task<Void, Never>] = [:] /// Manual refreshes tracked per scope so refreshing one provider neither greys out nor blocks /// a manual refresh of another. `.global` covers the all-providers refresh (⌘R / merged overview). var manualRefreshTasks: [ManualRefreshScope: Task<Void, Never>] = [:] var closedMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:] var closedMenuRebuildRequests = MenuRebuildRequestRegistry<ObjectIdentifier>() var openMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:] var openMenuRebuildRequests = MenuRebuildRequestRegistry<ObjectIdentifier>() var menuIdentitySignatures: [ObjectIdentifier: String] = [:] var codexAccountMenuProjectionRevalidationTask: Task<Void, Never>? var openMenuRebuildsClosingHostedSubviewMenus: Set<ObjectIdentifier> = [] var parentMenuRebuildPendingAfterHostedSubviewClose = false var deferredMenuInteractionRefreshProviders: Set<UsageProvider> = [] var deferredMenuInteractionRefreshPending: Bool { !self.deferredMenuInteractionRefreshProviders.isEmpty } var deferredOpenAIDashboardRefreshReason: String? var deferredMenuInteractionRefreshTask: Task<Void, Never>? var highlightedMenuItems: [ObjectIdentifier: NSMenuItem] = [:] var providerSwitcherShortcutEventMonitor: ProviderSwitcherShortcutEventMonitor? var providerSwitcherShortcutMenuID: ObjectIdentifier? var providerSwitcherPointerInteractionMenuID: ObjectIdentifier? var pendingProviderSwitcherPointerRebuild: PendingProviderSwitcherRebuild? var overviewScrollAccumulatedDelta: CGFloat = 0 var overviewScrollNavigationHandlerForTesting: ((OverviewScrollStep) -> Void)? var hasPreparedForAppShutdown = false var scheduleQuitTermination: (@escaping @MainActor () -> Void) -> Void = { operation in DispatchQueue.main.async { Task { @MainActor in operation() } } } var terminateApplicationForQuit: @MainActor () -> Void = { NSApp.terminate(nil) } var openMenuInvalidationRetryTask: Task<Void, Never>? #if DEBUG var onDelayedMenuRefreshAttemptForTesting: (() -> Void)? var onDeferredMenuInteractionRefreshForTesting: (() -> Void)? var onOpenMenuInvalidationRetryForTesting: (() -> Void)? var isReleasedForTesting = false var lastLoggedClosedMenuRebuildVersion: Int? var _test_openMenuRefreshYieldOverride: (@MainActor () async -> Void)? var _test_openMenuRebuildObserver: (@MainActor (NSMenu) -> Void)? var _test_providerSwitcherMenuRebuildDebounceNanoseconds: UInt64? var _test_codexAmbientLoginRunnerOverride: (@MainActor (TimeInterval) async -> CodexLoginRunner.Result)? var _test_manualRefreshOperation: (@MainActor () async -> Void)? #endif var blinkTask: Task<Void, Never>? var menuBarCountdownRefreshTask: Task<Void, Never>? var loginTask: Task<Void, Never>? { didSet { self.refreshMenusForLoginStateChange() } } var creditsPurchaseWindow: OpenAICreditsPurchaseWindowController? var activeLoginProvider: UsageProvider? { didSet { if oldValue != self.activeLoginProvider { self.refreshMenusForLoginStateChange() } } } var blinkStates: [UsageProvider: BlinkState] = [:] var blinkAmounts: [UsageProvider: CGFloat] = [:] var wiggleAmounts: [UsageProvider: CGFloat] = [:] var tiltAmounts: [UsageProvider: CGFloat] = [:] var quotaWarningFlashUntil: [UsageProvider: Date] = [:] var quotaWarningFlashTasks: [UsageProvider: Task<Void, Never>] = [:] var blinkForceUntil: Date? var loginPhase: LoginPhase = .idle { didSet { if oldValue != self.loginPhase { self.refreshMenusForLoginStateChange() } } } let preferencesSelection: PreferencesSelection var animationDriver: DisplayLinkDriver? var animationPhase: Double = 0 var animationPattern: LoadingPattern = .knightRider var animationStartedAt: Date? private var lastConfigRevision: Int private var lastProviderOrder: [UsageProvider] private var lastMergeIcons: Bool private var lastSwitcherShowsIcons: Bool private var lastObservedUsageBarsShowUsed: Bool /// Tracks which `usageBarsShowUsed` mode the provider switcher was built with. /// Used to decide whether we can "smart update" menu content without rebuilding the switcher. var lastSwitcherUsageBarsShowUsed: Bool /// Tracks whether the merged-menu switcher was built with the Overview tab visible. /// Used to force switcher rebuilds when Overview availability toggles. var lastSwitcherIncludesOverview: Bool = false /// Tracks localization-sensitive labels used by the merged menu. /// Used to force menu rebuilds when app language changes. var lastMenuLocalizationSignature: String = "" /// Tracks which providers the merged menu's switcher was built with, to detect when it needs full rebuild. var lastSwitcherProviders: [UsageProvider] = [] /// Tracks which switcher tab state was used for the current merged-menu switcher instance. var lastMergedSwitcherSelection: ProviderSwitcherSelection? /// Tracks which provider/overview content is currently attached below the merged-menu switcher. var lastMergedMenuContentSelection: ProviderSwitcherSelection? /// Tracks the visible Codex account switcher contents for merged-menu smart updates. var lastCodexAccountMenuDisplay: CodexAccountMenuDisplay? /// Tracks the visible token account switcher contents for merged-menu smart updates. var lastTokenAccountMenuDisplay: TokenAccountMenuDisplay? /// Keeps detached merged-menu tab content reusable while the same menu remains open. var mergedSwitcherContentCaches: [ObjectIdentifier: [ProviderSwitcherSelection: CachedMergedSwitcherMenuContent]] = [:] var preservesMergedSwitcherContentCachesDuringInvalidation = false /// Card hosting views harvested from items about to be discarded by the current populate /// pass, keyed by card identifier; consumed by `makeMenuCardItem` and cleared when the /// pass finishes. Never outlives a single synchronous menu population. var menuCardViewRecyclePool: [String: NSView] = [:] /// Monotonic token used to ignore stale deferred provider-switcher menu rebuilds. var providerSwitcherUpdateToken = 0 var providerSelectionUIRefreshTask: Task<Void, Never>? var deferredMergedIconRenderAfterTracking = false var lastAppliedMergedIconRenderSignature: String? var lastAppliedProviderIconRenderSignatures: [UsageProvider: String] = [:] var lastObservedStoreIconWorkSignature: String? var iconPerfRefreshCycleMetrics: IconPerfRefreshCycleMetrics? var iconPerfUpdatePassActive = false var lastKnownScreenCount: Int var pendingScreenChangePreviousCount: Int? var screenChangeVisibilityTask: Task<Void, Never>? let loginLogger = CodexBarLog.logger(LogCategories.login) let menuLogger = CodexBarLog.logger(LogCategories.app) var selectedMenuProvider: UsageProvider? { get { self.settings.selectedMenuProvider } set { self.settings.selectedMenuProvider = newValue } } private static func makeStatusItem( statusBar: NSStatusBar, identity: StatusItemIdentity, defaults: UserDefaults, legacyDefaultItemIndex: Int?) -> NSStatusItem { MenuBarStatusItemPlacementPreflight.prepare( defaults: defaults, autosaveName: identity.autosaveName, legacyDefaultItemIndex: legacyDefaultItemIndex) let item = statusBar.statusItem(withLength: NSStatusItem.variableLength) item.autosaveName = identity.autosaveName if let button = item.button { let title = self.statusItemAccessibilityTitle( isDebugApp: self.isDebugApp(bundleIdentifier: Bundle.main.bundleIdentifier)) // Ensure the icon is rendered at 1:1 without resampling (crisper edges for template images). button.imageScaling = .scaleNone button.setAccessibilityIdentifier(identity.accessibilityIdentifier) button.setAccessibilityTitle(title) button.toolTip = title } return item } struct BlinkState { var nextBlink: Date var blinkStart: Date? var pendingSecondStart: Date? var effect: MotionEffect = .blink static func randomDelay() -> TimeInterval { Double.random(in: 3...12) } } enum MotionEffect { case blink case wiggle case tilt } enum LoginPhase { case idle case requesting case waitingBrowser } func menuBarMetricWindow(for provider: UsageProvider, snapshot: UsageSnapshot?) -> RateWindow? { if provider == .codex { return self.codexMenuBarMetricWindow(snapshot: snapshot) } return MenuBarMetricWindowResolver.rateWindow( preference: self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot), provider: provider, snapshot: snapshot, supportsAverage: self.settings.menuBarMetricSupportsAverage(for: provider)) } private func codexMenuBarMetricWindow(snapshot: UsageSnapshot?) -> RateWindow? { guard let snapshot else { return nil } let projection = CodexConsumerProjection.make( surface: .menuBar, context: CodexConsumerProjection.Context( snapshot: snapshot, rawUsageError: nil, liveCredits: self.store.credits, rawCreditsError: self.store.lastCreditsError, liveDashboard: self.store.openAIDashboard, rawDashboardError: self.store.lastOpenAIDashboardError, dashboardAttachmentAuthorized: self.store.openAIDashboardAttachmentAuthorized, dashboardRequiresLogin: self.store.openAIDashboardRequiresLogin, now: snapshot.updatedAt)) let lanes = projection.visibleRateLanes let first = lanes.first.flatMap { projection.rateWindow(for: $0) } let second = lanes.dropFirst().first.flatMap { projection.rateWindow(for: $0) } let preference = self.settings.menuBarMetricPreference(for: .codex, snapshot: snapshot) switch preference { case .secondary, .tertiary: return second ?? first case .extraUsage: return first case .average: guard self.settings.menuBarMetricSupportsAverage(for: .codex), let primary = first, let secondary = second else { return first } let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2 return RateWindow( usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil) case .primaryAndSecondary: return [first, second].compactMap(\.self).max(by: { $0.usedPercent < $1.usedPercent }) case .automatic, .primary, .monthlyPlan: return first } } init( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, preferencesSelection: PreferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(), codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil, statusBar: NSStatusBar = .system, menuCardRenderingEnabled: Bool = StatusItemController.menuCardRenderingEnabled, menuRefreshEnabled: Bool = StatusItemController.menuRefreshEnabled, observeProviderConfigNotifications: Bool = !SettingsStore.isRunningTests) { if SettingsStore.isRunningTests { _ = NSApplication.shared } self.store = store self.settings = settings self.account = account self.updater = updater self.preferencesSelection = preferencesSelection self.managedCodexAccountCoordinator = managedCodexAccountCoordinator self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator ?? CodexAccountPromotionCoordinator( settingsStore: settings, usageStore: store, managedAccountCoordinator: managedCodexAccountCoordinator) self.lastConfigRevision = settings.configRevision self.lastProviderOrder = settings.providerOrder self.lastMergeIcons = settings.mergeIcons self.lastSwitcherShowsIcons = settings.switcherShowsIcons self.lastObservedUsageBarsShowUsed = settings.usageBarsShowUsed self.lastSwitcherUsageBarsShowUsed = settings.usageBarsShowUsed self.menuCardRenderingEnabledForController = menuCardRenderingEnabled self.menuRefreshEnabledForController = menuRefreshEnabled let repairedStatusItemVisibilityKeys = MenuBarStatusItemDefaultsRepair .repairHiddenVisibilityDefaultsIfNeeded(defaults: settings.userDefaults) self.statusBar = statusBar self.statusItem = Self.makeStatusItem( statusBar: statusBar, identity: .merged, defaults: settings.userDefaults, legacyDefaultItemIndex: Self.mergedLegacyDefaultItemIndex) self.lastKnownScreenCount = NSScreen.screens.count // Status items for individual providers are now created lazily in updateVisibility() super.init() if !repairedStatusItemVisibilityKeys.isEmpty { self.menuLogger.info( "Repaired hidden macOS status-item visibility defaults", metadata: ["keys": repairedStatusItemVisibilityKeys.joined(separator: ",")]) } self.lastMenuAdjunctReadinessSignature = self.menuAdjunctReadinessSignature() self.lastMenuAdjunctReadinessBaselineVersion = self.menuSession.contentVersion self.wireBindings() self.updateVisibility() self.updateIcons() self.scheduleCodexAccountMenuProjectionRevalidationIfNeeded( for: self.store.enabledProvidersForDisplay()) self.scheduleStartupStatusItemVisibilityCheck() NotificationCenter.default.addObserver( self, selector: #selector(self.handleDebugReplayNotification(_:)), name: .codexbarDebugReplayAllAnimations, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(self.handleDebugBlinkNotification), name: .codexbarDebugBlinkNow, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(self.handleQuotaWarningPosted(_:)), name: .codexbarQuotaWarningDidPost, object: nil) if observeProviderConfigNotifications { NotificationCenter.default.addObserver( self, selector: #selector(self.handleProviderConfigDidChange), name: .codexbarProviderConfigDidChange, object: nil) } NotificationCenter.default.addObserver( self, selector: #selector(self.handleScreenParametersDidChange(_:)), name: NSApplication.didChangeScreenParametersNotification, object: nil) } convenience init( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, preferencesSelection: PreferencesSelection, statusBar: NSStatusBar = .system, menuCardRenderingEnabled: Bool = StatusItemController.menuCardRenderingEnabled, menuRefreshEnabled: Bool = StatusItemController.menuRefreshEnabled, observeProviderConfigNotifications: Bool = !SettingsStore.isRunningTests) { self.init( store: store, settings: settings, account: account, updater: updater, preferencesSelection: preferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator(), codexAccountPromotionCoordinator: nil, statusBar: statusBar, menuCardRenderingEnabled: menuCardRenderingEnabled,Lines 1–500 of 1013. Load more
import AppKit import CodexBarCore import Observation import QuartzCore // MARK: - Status item controller (AppKit-hosted icons, SwiftUI popovers) @MainActor protocol StatusItemControlling: AnyObject { func openMenuFromShortcut() func runLoginFlowFromSettings(provider: UsageProvider) async func celebrationOriginPoint(for provider: UsageProvider?) -> CGPoint? func trimRebuildableCachesForMemoryPressure() -> MemoryPressureCacheTrimSummary #if DEBUG func seedRebuildableCachesForMemoryPressureProof() #endif func prepareForAppShutdown() } extension StatusItemControlling { func celebrationOriginPoint(for provider: UsageProvider?) -> CGPoint? { nil } func trimRebuildableCachesForMemoryPressure() -> MemoryPressureCacheTrimSummary { MemoryPressureCacheTrimSummary() } #if DEBUG func seedRebuildableCachesForMemoryPressureProof() {} #endif func prepareForAppShutdown() {} } @MainActor final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControlling { // Disable SwiftUI menu cards + menu refresh work in tests to avoid swiftpm-testing-helper crashes. static var menuCardRenderingEnabled = !SettingsStore.isRunningTests private static let defaultMenuRefreshEnabled = !SettingsStore.isRunningTests private(set) static var menuRefreshEnabled = !SettingsStore.isRunningTests static let quotaWarningFlashDuration: TimeInterval = 60 private nonisolated static let statusItemAccessibilityTitle = "CodexBar" private nonisolated static let debugStatusItemAccessibilityTitle = "CodexBar Debug" private nonisolated static let statusItemAccessibilityIdentifierPrefix = "CodexBar.StatusItem" private nonisolated static let mergedLegacyDefaultItemIndex = 0 enum StatusItemIdentity { case merged case provider(UsageProvider) var autosaveName: String { switch self { case .merged: "codexbar-merged" case let .provider(provider): "codexbar-\(provider.rawValue)" } } var accessibilityIdentifier: String { switch self { case .merged: StatusItemController.statusItemAccessibilityIdentifierPrefix case let .provider(provider): "\(StatusItemController.statusItemAccessibilityIdentifierPrefix).\(provider.rawValue)" } } } nonisolated static func isDebugApp(bundleIdentifier: String?) -> Bool { bundleIdentifier?.contains(".debug") == true } nonisolated static func statusItemAccessibilityTitle(isDebugApp: Bool) -> String { isDebugApp ? self.debugStatusItemAccessibilityTitle : self.statusItemAccessibilityTitle } #if DEBUG var menuRefreshEnabledOverrideForTesting: Bool? #endif typealias Factory = @MainActor ( UsageStore, SettingsStore, AccountInfo, UpdaterProviding, PreferencesSelection, ManagedCodexAccountCoordinator, CodexAccountPromotionCoordinator) -> StatusItemControlling // swiftlint:disable:next function_parameter_count static func makeDefaultController( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, selection: PreferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator, codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator) -> StatusItemControlling { StatusItemController( store: store, settings: settings, account: account, updater: updater, preferencesSelection: selection, managedCodexAccountCoordinator: managedCodexAccountCoordinator, codexAccountPromotionCoordinator: codexAccountPromotionCoordinator) } static let defaultFactory: Factory = StatusItemController.makeDefaultController static var factory: Factory = StatusItemController.defaultFactory let store: UsageStore let settings: SettingsStore lazy var menuCardRefreshMonitor = MenuCardRefreshMonitor { [weak self] provider in self?.menuCardModel(for: provider) } let account: AccountInfo let updater: UpdaterProviding let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator let statusBar: NSStatusBar let menuCardRenderingEnabledForController: Bool let menuRefreshEnabledForController: Bool var statusItem: NSStatusItem var statusItems: [UsageProvider: NSStatusItem] = [:] var lastMenuProvider: UsageProvider? var menuProviders: [ObjectIdentifier: UsageProvider] = [:] var menuSession = MenuSessionCoordinator<ObjectIdentifier>() var menuReadinessSignatures: [ObjectIdentifier: String] = [:] let hostedSubviewRenderSignatures = NSMapTable<NSMenu, NSString>.weakToStrongObjects() /// Persistent Refresh rows are weakly tracked so their enabled state can change during menu tracking. let persistentRefreshItems = NSHashTable<NSMenuItem>.weakObjects() var menuCardHeightCache: [MenuCardHeightCacheKey: CGFloat] = [:] var measuredStandardMenuWidthCache: [String: CGFloat] = [:] var lastMenuAdjunctReadinessSignature = "" var lastMenuAdjunctReadinessBaselineVersion = 0 var rootOpenHandledMenuObservationSignature: String? var mergedMenu: NSMenu? var providerMenus: [UsageProvider: NSMenu] = [:] var fallbackMenu: NSMenu? var openMenus: [ObjectIdentifier: NSMenu] = [:] var menuRefreshTasks: [ObjectIdentifier: Task<Void, Never>] = [:] /// Manual refreshes tracked per scope so refreshing one provider neither greys out nor blocks /// a manual refresh of another. `.global` covers the all-providers refresh (⌘R / merged overview). var manualRefreshTasks: [ManualRefreshScope: Task<Void, Never>] = [:] var closedMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:] var closedMenuRebuildRequests = MenuRebuildRequestRegistry<ObjectIdentifier>() var openMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:] var openMenuRebuildRequests = MenuRebuildRequestRegistry<ObjectIdentifier>() var menuIdentitySignatures: [ObjectIdentifier: String] = [:] var codexAccountMenuProjectionRevalidationTask: Task<Void, Never>? var openMenuRebuildsClosingHostedSubviewMenus: Set<ObjectIdentifier> = [] var parentMenuRebuildPendingAfterHostedSubviewClose = false var deferredMenuInteractionRefreshProviders: Set<UsageProvider> = [] var deferredMenuInteractionRefreshPending: Bool { !self.deferredMenuInteractionRefreshProviders.isEmpty } var deferredOpenAIDashboardRefreshReason: String? var deferredMenuInteractionRefreshTask: Task<Void, Never>? var highlightedMenuItems: [ObjectIdentifier: NSMenuItem] = [:] var providerSwitcherShortcutEventMonitor: ProviderSwitcherShortcutEventMonitor? var providerSwitcherShortcutMenuID: ObjectIdentifier? var providerSwitcherPointerInteractionMenuID: ObjectIdentifier? var pendingProviderSwitcherPointerRebuild: PendingProviderSwitcherRebuild? var overviewScrollAccumulatedDelta: CGFloat = 0 var overviewScrollNavigationHandlerForTesting: ((OverviewScrollStep) -> Void)? var hasPreparedForAppShutdown = false var scheduleQuitTermination: (@escaping @MainActor () -> Void) -> Void = { operation in DispatchQueue.main.async { Task { @MainActor in operation() } } } var terminateApplicationForQuit: @MainActor () -> Void = { NSApp.terminate(nil) } var openMenuInvalidationRetryTask: Task<Void, Never>? #if DEBUG var onDelayedMenuRefreshAttemptForTesting: (() -> Void)? var onDeferredMenuInteractionRefreshForTesting: (() -> Void)? var onOpenMenuInvalidationRetryForTesting: (() -> Void)? var isReleasedForTesting = false var lastLoggedClosedMenuRebuildVersion: Int? var _test_openMenuRefreshYieldOverride: (@MainActor () async -> Void)? var _test_openMenuRebuildObserver: (@MainActor (NSMenu) -> Void)? var _test_providerSwitcherMenuRebuildDebounceNanoseconds: UInt64? var _test_codexAmbientLoginRunnerOverride: (@MainActor (TimeInterval) async -> CodexLoginRunner.Result)? var _test_manualRefreshOperation: (@MainActor () async -> Void)? #endif var blinkTask: Task<Void, Never>? var menuBarCountdownRefreshTask: Task<Void, Never>? var loginTask: Task<Void, Never>? { didSet { self.refreshMenusForLoginStateChange() } } var creditsPurchaseWindow: OpenAICreditsPurchaseWindowController? var activeLoginProvider: UsageProvider? { didSet { if oldValue != self.activeLoginProvider { self.refreshMenusForLoginStateChange() } } } var blinkStates: [UsageProvider: BlinkState] = [:] var blinkAmounts: [UsageProvider: CGFloat] = [:] var wiggleAmounts: [UsageProvider: CGFloat] = [:] var tiltAmounts: [UsageProvider: CGFloat] = [:] var quotaWarningFlashUntil: [UsageProvider: Date] = [:] var quotaWarningFlashTasks: [UsageProvider: Task<Void, Never>] = [:] var blinkForceUntil: Date? var loginPhase: LoginPhase = .idle { didSet { if oldValue != self.loginPhase { self.refreshMenusForLoginStateChange() } } } let preferencesSelection: PreferencesSelection var animationDriver: DisplayLinkDriver? var animationPhase: Double = 0 var animationPattern: LoadingPattern = .knightRider var animationStartedAt: Date? private var lastConfigRevision: Int private var lastProviderOrder: [UsageProvider] private var lastMergeIcons: Bool private var lastSwitcherShowsIcons: Bool private var lastObservedUsageBarsShowUsed: Bool /// Tracks which `usageBarsShowUsed` mode the provider switcher was built with. /// Used to decide whether we can "smart update" menu content without rebuilding the switcher. var lastSwitcherUsageBarsShowUsed: Bool /// Tracks whether the merged-menu switcher was built with the Overview tab visible. /// Used to force switcher rebuilds when Overview availability toggles. var lastSwitcherIncludesOverview: Bool = false /// Tracks localization-sensitive labels used by the merged menu. /// Used to force menu rebuilds when app language changes. var lastMenuLocalizationSignature: String = "" /// Tracks which providers the merged menu's switcher was built with, to detect when it needs full rebuild. var lastSwitcherProviders: [UsageProvider] = [] /// Tracks which switcher tab state was used for the current merged-menu switcher instance. var lastMergedSwitcherSelection: ProviderSwitcherSelection? /// Tracks which provider/overview content is currently attached below the merged-menu switcher. var lastMergedMenuContentSelection: ProviderSwitcherSelection? /// Tracks the visible Codex account switcher contents for merged-menu smart updates. var lastCodexAccountMenuDisplay: CodexAccountMenuDisplay? /// Tracks the visible token account switcher contents for merged-menu smart updates. var lastTokenAccountMenuDisplay: TokenAccountMenuDisplay? /// Keeps detached merged-menu tab content reusable while the same menu remains open. var mergedSwitcherContentCaches: [ObjectIdentifier: [ProviderSwitcherSelection: CachedMergedSwitcherMenuContent]] = [:] var preservesMergedSwitcherContentCachesDuringInvalidation = false /// Card hosting views harvested from items about to be discarded by the current populate /// pass, keyed by card identifier; consumed by `makeMenuCardItem` and cleared when the /// pass finishes. Never outlives a single synchronous menu population. var menuCardViewRecyclePool: [String: NSView] = [:] /// Monotonic token used to ignore stale deferred provider-switcher menu rebuilds. var providerSwitcherUpdateToken = 0 var providerSelectionUIRefreshTask: Task<Void, Never>? var deferredMergedIconRenderAfterTracking = false var lastAppliedMergedIconRenderSignature: String? var lastAppliedProviderIconRenderSignatures: [UsageProvider: String] = [:] var lastObservedStoreIconWorkSignature: String? var iconPerfRefreshCycleMetrics: IconPerfRefreshCycleMetrics? var iconPerfUpdatePassActive = false var lastKnownScreenCount: Int var pendingScreenChangePreviousCount: Int? var screenChangeVisibilityTask: Task<Void, Never>? let loginLogger = CodexBarLog.logger(LogCategories.login) let menuLogger = CodexBarLog.logger(LogCategories.app) var selectedMenuProvider: UsageProvider? { get { self.settings.selectedMenuProvider } set { self.settings.selectedMenuProvider = newValue } } private static func makeStatusItem( statusBar: NSStatusBar, identity: StatusItemIdentity, defaults: UserDefaults, legacyDefaultItemIndex: Int?) -> NSStatusItem { MenuBarStatusItemPlacementPreflight.prepare( defaults: defaults, autosaveName: identity.autosaveName, legacyDefaultItemIndex: legacyDefaultItemIndex) let item = statusBar.statusItem(withLength: NSStatusItem.variableLength) item.autosaveName = identity.autosaveName if let button = item.button { let title = self.statusItemAccessibilityTitle( isDebugApp: self.isDebugApp(bundleIdentifier: Bundle.main.bundleIdentifier)) // Ensure the icon is rendered at 1:1 without resampling (crisper edges for template images). button.imageScaling = .scaleNone button.setAccessibilityIdentifier(identity.accessibilityIdentifier) button.setAccessibilityTitle(title) button.toolTip = title } return item } struct BlinkState { var nextBlink: Date var blinkStart: Date? var pendingSecondStart: Date? var effect: MotionEffect = .blink static func randomDelay() -> TimeInterval { Double.random(in: 3...12) } } enum MotionEffect { case blink case wiggle case tilt } enum LoginPhase { case idle case requesting case waitingBrowser } func menuBarMetricWindow(for provider: UsageProvider, snapshot: UsageSnapshot?) -> RateWindow? { if provider == .codex { return self.codexMenuBarMetricWindow(snapshot: snapshot) } return MenuBarMetricWindowResolver.rateWindow( preference: self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot), provider: provider, snapshot: snapshot, supportsAverage: self.settings.menuBarMetricSupportsAverage(for: provider)) } private func codexMenuBarMetricWindow(snapshot: UsageSnapshot?) -> RateWindow? { guard let snapshot else { return nil } let projection = CodexConsumerProjection.make( surface: .menuBar, context: CodexConsumerProjection.Context( snapshot: snapshot, rawUsageError: nil, liveCredits: self.store.credits, rawCreditsError: self.store.lastCreditsError, liveDashboard: self.store.openAIDashboard, rawDashboardError: self.store.lastOpenAIDashboardError, dashboardAttachmentAuthorized: self.store.openAIDashboardAttachmentAuthorized, dashboardRequiresLogin: self.store.openAIDashboardRequiresLogin, now: snapshot.updatedAt)) let lanes = projection.visibleRateLanes let first = lanes.first.flatMap { projection.rateWindow(for: $0) } let second = lanes.dropFirst().first.flatMap { projection.rateWindow(for: $0) } let preference = self.settings.menuBarMetricPreference(for: .codex, snapshot: snapshot) switch preference { case .secondary, .tertiary: return second ?? first case .extraUsage: return first case .average: guard self.settings.menuBarMetricSupportsAverage(for: .codex), let primary = first, let secondary = second else { return first } let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2 return RateWindow( usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil) case .primaryAndSecondary: return [first, second].compactMap(\.self).max(by: { $0.usedPercent < $1.usedPercent }) case .automatic, .primary, .monthlyPlan: return first } } init( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, preferencesSelection: PreferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(), codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil, statusBar: NSStatusBar = .system, menuCardRenderingEnabled: Bool = StatusItemController.menuCardRenderingEnabled, menuRefreshEnabled: Bool = StatusItemController.menuRefreshEnabled, observeProviderConfigNotifications: Bool = !SettingsStore.isRunningTests) { if SettingsStore.isRunningTests { _ = NSApplication.shared } self.store = store self.settings = settings self.account = account self.updater = updater self.preferencesSelection = preferencesSelection self.managedCodexAccountCoordinator = managedCodexAccountCoordinator self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator ?? CodexAccountPromotionCoordinator( settingsStore: settings, usageStore: store, managedAccountCoordinator: managedCodexAccountCoordinator) self.lastConfigRevision = settings.configRevision self.lastProviderOrder = settings.providerOrder self.lastMergeIcons = settings.mergeIcons self.lastSwitcherShowsIcons = settings.switcherShowsIcons self.lastObservedUsageBarsShowUsed = settings.usageBarsShowUsed self.lastSwitcherUsageBarsShowUsed = settings.usageBarsShowUsed self.menuCardRenderingEnabledForController = menuCardRenderingEnabled self.menuRefreshEnabledForController = menuRefreshEnabled let repairedStatusItemVisibilityKeys = MenuBarStatusItemDefaultsRepair .repairHiddenVisibilityDefaultsIfNeeded(defaults: settings.userDefaults) self.statusBar = statusBar self.statusItem = Self.makeStatusItem( statusBar: statusBar, identity: .merged, defaults: settings.userDefaults, legacyDefaultItemIndex: Self.mergedLegacyDefaultItemIndex) self.lastKnownScreenCount = NSScreen.screens.count // Status items for individual providers are now created lazily in updateVisibility() super.init() if !repairedStatusItemVisibilityKeys.isEmpty { self.menuLogger.info( "Repaired hidden macOS status-item visibility defaults", metadata: ["keys": repairedStatusItemVisibilityKeys.joined(separator: ",")]) } self.lastMenuAdjunctReadinessSignature = self.menuAdjunctReadinessSignature() self.lastMenuAdjunctReadinessBaselineVersion = self.menuSession.contentVersion self.wireBindings() self.updateVisibility() self.updateIcons() self.scheduleCodexAccountMenuProjectionRevalidationIfNeeded( for: self.store.enabledProvidersForDisplay()) self.scheduleStartupStatusItemVisibilityCheck() NotificationCenter.default.addObserver( self, selector: #selector(self.handleDebugReplayNotification(_:)), name: .codexbarDebugReplayAllAnimations, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(self.handleDebugBlinkNotification), name: .codexbarDebugBlinkNow, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(self.handleQuotaWarningPosted(_:)), name: .codexbarQuotaWarningDidPost, object: nil) if observeProviderConfigNotifications { NotificationCenter.default.addObserver( self, selector: #selector(self.handleProviderConfigDidChange), name: .codexbarProviderConfigDidChange, object: nil) } NotificationCenter.default.addObserver( self, selector: #selector(self.handleScreenParametersDidChange(_:)), name: NSApplication.didChangeScreenParametersNotification, object: nil) } convenience init( store: UsageStore, settings: SettingsStore, account: AccountInfo, updater: UpdaterProviding, preferencesSelection: PreferencesSelection, statusBar: NSStatusBar = .system, menuCardRenderingEnabled: Bool = StatusItemController.menuCardRenderingEnabled, menuRefreshEnabled: Bool = StatusItemController.menuRefreshEnabled, observeProviderConfigNotifications: Bool = !SettingsStore.isRunningTests) { self.init( store: store, settings: settings, account: account, updater: updater, preferencesSelection: preferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator(), codexAccountPromotionCoordinator: nil, statusBar: statusBar, menuCardRenderingEnabled: menuCardRenderingEnabled,