Zum Inhalt

macos/Sources/PerlinLiveWallpaper/PowerMonitor.swift

import Foundation
import IOKit.ps

/// Reports the power source — running on battery vs. plugged into AC. Drives the
/// wallpaper's power policy: full rate on AC, paused (or reduced) on battery.
/// Polls every few seconds; plug/unplug latency of a few seconds is irrelevant
/// for a wallpaper, and polling avoids the IOPS C-callback dance.
final class PowerMonitor {
    /// Called with `true` when the Mac goes onto battery, `false` when back on AC.
    var onChange: ((Bool) -> Void)?
    private(set) var onBattery = false
    private var timer: Timer?

    func start() {
        onBattery = PowerMonitor.isOnBattery()
        let timer = Timer(timeInterval: 5, repeats: true) { [weak self] _ in self?.evaluate() }
        RunLoop.main.add(timer, forMode: .common)
        self.timer = timer
    }

    deinit { timer?.invalidate() }

    private func evaluate() {
        let battery = PowerMonitor.isOnBattery()
        guard battery != onBattery else { return }
        onBattery = battery
        onChange?(battery)
    }

    /// True when running on battery. A desktop Mac (no battery source) → false.
    static func isOnBattery() -> Bool {
        guard let blob = IOPSCopyPowerSourcesInfo()?.takeRetainedValue(),
              let list = IOPSCopyPowerSourcesList(blob)?.takeRetainedValue() as? [CFTypeRef] else {
            return false
        }
        for source in list {
            guard let desc = IOPSGetPowerSourceDescription(blob, source)?.takeUnretainedValue() as? [String: Any],
                  let state = desc[kIOPSPowerSourceStateKey] as? String else { continue }
            if state == kIOPSBatteryPowerValue { return true }
        }
        return false
    }
}