-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
BatteryInfo.swift
97 lines (82 loc) · 2.82 KB
/
BatteryInfo.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import IOKit.ps
import Foundation
final class BatteryInfo: @unchecked Sendable {
struct Model: Equatable {
let acPowered: Bool?
let currentCapacity: Float
let isLowPowerModeEnabled: Bool
let isCharged: Bool?
let isCharging: Bool?
init(acPowered: Bool?,
currentCapacity: Float,
isLowPowerModeEnabled: Bool = ProcessInfo.processInfo.isLowPowerModeEnabled,
isCharging: Bool?,
isCharged: Bool?) {
self.acPowered = acPowered
self.currentCapacity = currentCapacity
self.isLowPowerModeEnabled = isLowPowerModeEnabled
self.isCharged = isCharged
self.isCharging = isCharging
}
}
static let shared: BatteryInfo = BatteryInfo()
@Published private(set) var data: Model?
private init() {
subscribe()
data = getBatteryInfo()
}
// Based on https://stackoverflow.com/a/57145146
private func getBatteryInfo() -> Model? {
let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AppleSmartBattery"))
defer {
IOServiceClose(service)
IOObjectRelease(service)
}
let powerSourceInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let powerSources = IOPSCopyPowerSourcesList(powerSourceInfo).takeRetainedValue() as Array
if powerSources.isEmpty {
// If there aren't any power sources, we're probably running on a desktop.
// So let's unsubscribe from battery events.
unsubscribe()
return nil
}
var batteryLevel: Float = -1
for powerSource in powerSources {
let powerSourceInfo = IOPSGetPowerSourceDescription(powerSourceInfo, powerSource).takeUnretainedValue() as! [String: Any]
if let currentCapacity = powerSourceInfo[kIOPSCurrentCapacityKey] as? Int,
let maxCapacity = powerSourceInfo[kIOPSMaxCapacityKey] as? Int {
batteryLevel = Float(currentCapacity) / Float(maxCapacity)
}
}
return Model(
acPowered: service.bool("ExternalConnected"),
currentCapacity: batteryLevel,
isCharging: service.bool("IsCharging"),
isCharged: service.bool("FullyCharged")
)
}
private func subscribe() {
NotificationCenter.default.addObserver(
self,
selector: #selector(batteryLevelDidChange(_:)),
name: NSNotification.Name.NSProcessInfoPowerStateDidChange,
object: nil
)
}
private func unsubscribe() {
NotificationCenter.default.removeObserver(
self,
name: NSNotification.Name.NSProcessInfoPowerStateDidChange,
object: nil
)
}
@objc private func batteryLevelDidChange(_ notification: Notification) {
data = getBatteryInfo()
}
}
fileprivate extension io_service_t {
func bool(_ forIdentifier: String) -> Bool? {
IORegistryEntryCreateCFProperty(self, forIdentifier as CFString, kCFAllocatorDefault, 0)
.takeRetainedValue() as? Bool
}
}