943 lines · 38.5 KB · Swift
RawDownload
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 prepareForAppShutdown()} extension StatusItemControlling {    func celebrationOriginPoint(for provider: UsageProvider?) -> CGPoint? {        nil    }     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 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)"            }        }    }     #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        guard let model = self?.menuCardModel(for: provider) else { return nil }        return MenuCardLiveSubtitle(text: model.subtitleText, style: model.subtitleStyle)    }     let account: AccountInfo    let updater: UpdaterProviding    let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator    let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator    let statusBar: NSStatusBar    var statusItem: NSStatusItem    var statusItems: [UsageProvider: NSStatusItem] = [:]    var lastMenuProvider: UsageProvider?    var menuProviders: [ObjectIdentifier: UsageProvider] = [:]    var menuContentVersion: Int = 0    var latestRequiredMenuRebuildVersion: Int = 0    var latestDataOnlyMenuContentVersion: Int = 0    var latestStructuralMenuContentVersion: Int = 0    var menuVersions: [ObjectIdentifier: Int] = [:]    var menuReadinessSignatures: [ObjectIdentifier: String] = [:]    let hostedSubviewRenderSignatures = NSMapTable<NSMenu, NSString>.weakToStrongObjects()    /// Live persistent Refresh rows, tracked weakly so they can be given in-place in-flight    /// feedback (spinner) while the menu stays open, without rebuilding the menu.    let persistentRefreshRows = NSHashTable<PersistentMenuActionItemView>.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>] = [:]    var manualRefreshTask: Task<Void, Never>?    var closedMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:]    var closedMenuRebuildTokens: [ObjectIdentifier: Int] = [:]    var closedMenuRebuildTokenCounter = 0    var closedMenusDeferredUntilNextOpen: Set<ObjectIdentifier> = []    var openMenuRebuildTasks: [ObjectIdentifier: Task<Void, Never>] = [:]    var openMenuRebuildTokens: [ObjectIdentifier: Int] = [:]    var openMenuRebuildTokenCounter = 0    var menuIdentitySignatures: [ObjectIdentifier: String] = [:]    var codexAccountMenuProjectionRevalidationTask: Task<Void, Never>?    var openMenuRebuildsClosingHostedSubviewMenus: Set<ObjectIdentifier> = []    var parentMenuRebuildsDeferredDuringTracking: Set<ObjectIdentifier> = []    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 {            // Ensure the icon is rendered at 1:1 without resampling (crisper edges for template images).            button.imageScaling = .scaleNone            button.setAccessibilityIdentifier(identity.accessibilityIdentifier)            button.setAccessibilityTitle(self.statusItemAccessibilityTitle)            button.toolTip = self.statusItemAccessibilityTitle        }        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 .automatic, .primary:            return first        }    }     init(        store: UsageStore,        settings: SettingsStore,        account: AccountInfo,        updater: UpdaterProviding,        preferencesSelection: PreferencesSelection,        managedCodexAccountCoordinator: ManagedCodexAccountCoordinator =            ManagedCodexAccountCoordinator(),        codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil,        statusBar: NSStatusBar = .system,        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        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.menuContentVersion        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,        observeProviderConfigNotifications: Bool = !SettingsStore.isRunningTests)    {        self.init(            store: store,            settings: settings,            account: account,            updater: updater,            preferencesSelection: preferencesSelection,            managedCodexAccountCoordinator: ManagedCodexAccountCoordinator(),            codexAccountPromotionCoordinator: nil,            statusBar: statusBar,            observeProviderConfigNotifications: observeProviderConfigNotifications)    }     private func wireBindings() {        self.observeStoreChanges()        self.observeStoreIconChanges()        self.observeIconPerfRefreshCycleChanges()        self.observeDebugForceAnimation()        self.observeSettingsChanges()        self.observeUpdaterChanges()        self.observeManagedCodexCoordinatorChanges()    }     private func observeStoreChanges() {        withObservationTracking {            _ = self.store.menuObservationToken        } onChange: { [weak self] in            Task { @MainActor [weak self] in                guard let self else { return }                self.handleObservedStoreMenuChange()            }        }    }     func handleObservedStoreMenuChange() {        self.observeStoreChanges()        // In-place spinner sync for the persistent Refresh rows. Safe during menu tracking:        // it mutates existing row views only and never rebuilds the menu.

Lines 1–500 of 943.