import Foundation import IOKit.ps /// Lightweight helper for querying macOS battery charging status and ETA. final class MacBatteryManager { static let shared = MacBatteryManager() private init() {} struct BatteryStatus { let timeRemainingMinutes: Int? let isCharging: Bool let percentage: Int? } func currentStatus() -> BatteryStatus { guard let sourcesInfo = IOPSCopyPowerSourcesInfo()?.takeRetainedValue(), let sourcesList = IOPSCopyPowerSourcesList(sourcesInfo)?.takeRetainedValue() as? [CFTypeRef] else { return BatteryStatus(timeRemainingMinutes: nil, isCharging: false, percentage: nil) } for source in sourcesList { guard let description = IOPSGetPowerSourceDescription(sourcesInfo, source)?.takeUnretainedValue() as? [String: Any], let type = description[kIOPSTypeKey] as? String, type == kIOPSInternalBatteryType else { continue } let isCharging = description[kIOPSIsChargingKey] as? Bool ?? false let timeRemaining = description[kIOPSTimeToFullChargeKey] as? Int let currentCapacity = description[kIOPSCurrentCapacityKey] as? Int let maxCapacity = description[kIOPSMaxCapacityKey] as? Int let percentage: Int? if let current = currentCapacity, let max = maxCapacity, max > 0 { percentage = (current * 100) / max } else { percentage = nil } return BatteryStatus( timeRemainingMinutes: timeRemaining, isCharging: isCharging, percentage: percentage ) } return BatteryStatus(timeRemainingMinutes: nil, isCharging: false, percentage: nil) } func formattedTimeToFullCharge() -> String? { let status = currentStatus() guard status.isCharging, let minutes = status.timeRemainingMinutes, minutes > 0 else { return nil } let hours = minutes / 60 let remainingMinutes = minutes % 60 if hours > 0 { return "\(hours)h \(remainingMinutes)m" } return "\(remainingMinutes)m" } }