import Foundation import Metrics import Observation import ServiceManagement import SMC /// 应用状态总入口,注入到所有 SwiftUI 视图 @MainActor @Observable public final class AppModel { public let settings: AppSettings public let store: MetricsStore public let helper: HelperClient public let fans: FanController public let keepAwake: KeepAwakeController public let cleaner: CleanerController public let maintenance: MaintenanceController public let network: NetworkController public let explainer = ProcessExplainer() @ObservationIgnored public let hub = MetricsHub() public var isMainWindowVisible = false /// 当前打开的菜单栏详情弹窗 public var openPopover: MenuBarItem? public private(set) var launchAtLoginEnabled = SMAppService.mainApp.status == .enabled public private(set) var launchAtLoginError: String? @ObservationIgnored var openSettings: () -> Void = {} @ObservationIgnored var openMainWindow: (PanelTab?) -> Void = { _ in } @ObservationIgnored var quit: () -> Void = {} public init(settings: AppSettings = AppSettings()) { let store = MetricsStore() let helper = HelperClient() self.settings = settings self.store = store self.helper = helper fans = FanController(helper: helper, store: store, settings: settings) keepAwake = KeepAwakeController(helper: helper, settings: settings) cleaner = CleanerController(settings: settings) maintenance = MaintenanceController(helper: helper) network = NetworkController(settings: settings) } /// 根据当前可见内容决定采集范围:主窗口看标签页,详情弹窗看是哪一项 var demand: MetricsDemand { var demand = MetricsDemand() let tab = settings.panelTab let window = isMainWindowVisible let popover = openPopover let menu = settings.menuBarItems let thermalPopover = popover == .temperature || popover == .fan demand.interval = window || popover != nil ? .seconds(1) : .seconds(settings.refreshSeconds) demand.memory = true demand.network = true let showing = { (page: PanelTab, item: MenuBarItem) in (window && tab == page) || popover == item } demand.gpu = (window && tab == .overview) || menu.contains(.gpu) || showing(.gpu, .gpu) demand.disk = window && (tab == .overview || tab == .cleaner) demand.battery = (window && (tab == .overview || tab == .keepAwake)) || keepAwake.lidClosedActive demand.processes = (window && [.processes, .overview].contains(tab)) || showing(.cpu, .cpu) || showing(.memory, .memory) var groups = Set() if window && tab == .overview { groups.formUnion([.cpu, .gpu]) } if (window && tab == .thermal) || thermalPopover { groups.formUnion(TemperatureGroup.allCases) } if menu.contains(.temperature) || fans.mode != .automatic || showing(.cpu, .cpu) { groups.insert(.cpu) } if showing(.gpu, .gpu) { groups.insert(.gpu) } demand.temperatures = groups demand.fans = (window && (tab == .thermal || tab == .overview)) || menu.contains(.fan) || fans.mode != .automatic || thermalPopover return demand } /// 连接探测在菜单栏显示网络项或网络详情打开时运行 var networkShown: Bool { settings.menuBarItems.contains(.network) || isNetworkDetailVisible } /// 网络详情(接口、公网 IP、进程流量)正在显示 var isNetworkDetailVisible: Bool { openPopover == .network || (isMainWindowVisible && settings.panelTab == .network) } func refreshLaunchAtLogin() { launchAtLoginEnabled = SMAppService.mainApp.status == .enabled } func setLaunchAtLogin(_ enabled: Bool) { launchAtLoginError = nil do { if enabled { try SMAppService.mainApp.register() } else { try SMAppService.mainApp.unregister() } } catch { launchAtLoginError = error.localizedDescription } refreshLaunchAtLogin() } /// 快捷切换风扇:未安装辅助工具时打开散热页引导安装 func requestFanMode(_ mode: FanController.Mode) { guard helper.isReady else { settings.panelTab = .thermal if !isMainWindowVisible { openMainWindow(.thermal) } return } Task { await fans.select(mode) } } /// 概览页快捷开关合盖运行:同时开启防休眠 func requestLidMode(_ enabled: Bool) { guard helper.isReady else { settings.panelTab = .keepAwake if !isMainWindowVisible { openMainWindow(.keepAwake) } return } Task { await keepAwake.setLidClosed(enabled) if enabled, !keepAwake.isActive { await keepAwake.start() } } } /// 用 Apple 智能解释进程:结果显示在主窗口的进程页 func explainProcess(_ subject: ProcessExplainer.Subject) { settings.panelTab = .processes if !isMainWindowVisible { openMainWindow(.processes) } explainer.explain(subject) } func handle(_ snapshot: MetricsSnapshot) { store.apply(snapshot) fans.evaluateSafety() keepAwake.evaluateBattery(store.battery) } }