mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
working temps and freq
This commit is contained in:
parent
fc522ccf84
commit
0f64d69680
9 changed files with 1222 additions and 9 deletions
|
|
@ -14350,6 +14350,10 @@
|
|||
},
|
||||
"Sensitivity" : {
|
||||
|
||||
},
|
||||
"Sensors" : {
|
||||
"comment" : "A label displayed above the group of CPU sensor gauges.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Session · ↓ %@ · ↑ %@" : {
|
||||
"comment" : "A row displaying the total downloaded and uploaded bytes for the network session.",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ struct CPUStatsDetailView: View {
|
|||
coreCount: statsManager.cpuCoreUsage.count,
|
||||
systemColor: systemColor,
|
||||
userColor: userColor,
|
||||
idleColor: idleColor
|
||||
idleColor: idleColor,
|
||||
temperature: statsManager.cpuTemperature,
|
||||
frequency: statsManager.cpuFrequency
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ struct CPUUsageDashboard: View {
|
|||
let systemColor: Color
|
||||
let userColor: Color
|
||||
let idleColor: Color
|
||||
let temperature: CPUTemperatureMetrics
|
||||
let frequency: CPUFrequencyMetrics?
|
||||
|
||||
var body: some View {
|
||||
let usageSection = VStack(alignment: .leading, spacing: 12) {
|
||||
|
|
@ -85,6 +87,14 @@ struct CPUUsageDashboard: View {
|
|||
DetailRow(color: nil, label: "Load 15m", value: String(format: "%.2f", loadAverage.fifteenMinutes))
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
let sensorsSection = CPUSensorGaugeGroup(
|
||||
temperature: temperature,
|
||||
frequency: frequency,
|
||||
usageTint: userColor,
|
||||
temperatureTint: Color(red: 0.98, green: 0.53, blue: 0.18)
|
||||
)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
return ViewThatFits(in: .horizontal) {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
|
|
@ -97,19 +107,23 @@ struct CPUUsageDashboard: View {
|
|||
.frame(width: 132, height: 132)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
sensorsSection
|
||||
usageSection
|
||||
Divider().padding(.vertical, 4)
|
||||
systemSection
|
||||
}
|
||||
HStack(alignment: .top, spacing: 28) {
|
||||
CPUSegmentDonut(
|
||||
breakdown: breakdown,
|
||||
systemColor: systemColor,
|
||||
userColor: userColor,
|
||||
idleColor: idleColor
|
||||
)
|
||||
.frame(width: 132, height: 132)
|
||||
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
CPUSegmentDonut(
|
||||
breakdown: breakdown,
|
||||
systemColor: systemColor,
|
||||
userColor: userColor,
|
||||
idleColor: idleColor
|
||||
)
|
||||
.frame(width: 132, height: 132)
|
||||
|
||||
sensorsSection
|
||||
}
|
||||
usageSection
|
||||
systemSection
|
||||
}
|
||||
|
|
@ -117,6 +131,95 @@ struct CPUUsageDashboard: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct CPUSensorGaugeGroup: View {
|
||||
let temperature: CPUTemperatureMetrics
|
||||
let frequency: CPUFrequencyMetrics?
|
||||
let usageTint: Color
|
||||
let temperatureTint: Color
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Sensors")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
HStack(spacing: 18) {
|
||||
CPUTemperatureGauge(temperature: temperature.celsius, tint: temperatureTint)
|
||||
CPUFrequencyGauge(frequency: frequency, tint: usageTint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct CPUTemperatureGauge: View {
|
||||
let temperature: Double?
|
||||
let tint: Color
|
||||
private let maxTemperature: Double = 110
|
||||
|
||||
var body: some View {
|
||||
CircularGaugeView(
|
||||
title: "Temperature",
|
||||
value: normalizedValue,
|
||||
tint: tint,
|
||||
centerPrimaryText: centerPrimary,
|
||||
subtitle: nil,
|
||||
size: 84,
|
||||
lineWidth: 9
|
||||
)
|
||||
}
|
||||
|
||||
private var normalizedValue: Double {
|
||||
guard let temperature, maxTemperature > 0 else { return 0 }
|
||||
return min(max(temperature / maxTemperature, 0), 1)
|
||||
}
|
||||
|
||||
private var centerPrimary: String {
|
||||
guard let temperature else { return "—" }
|
||||
return String(format: "%.0f°", temperature)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CPUFrequencyGauge: View {
|
||||
let frequency: CPUFrequencyMetrics?
|
||||
let tint: Color
|
||||
|
||||
var body: some View {
|
||||
CircularGaugeView(
|
||||
title: "Frequency",
|
||||
value: normalizedValue,
|
||||
tint: tint,
|
||||
centerPrimaryText: centerPrimary,
|
||||
centerSecondaryText: frequency != nil ? "GHz" : nil,
|
||||
subtitle: subtitle,
|
||||
size: 84,
|
||||
lineWidth: 9
|
||||
)
|
||||
}
|
||||
|
||||
private var normalizedValue: Double {
|
||||
guard let frequency else { return 0 }
|
||||
let candidateMax = frequency.maxOverallGHz > 0 ? frequency.maxOverallGHz : frequency.overallGHz
|
||||
let denominator = max(candidateMax, 1.0)
|
||||
return min(max(frequency.overallGHz / denominator, 0), 1)
|
||||
}
|
||||
|
||||
private var centerPrimary: String {
|
||||
guard let frequency else { return "—" }
|
||||
return String(format: "%.1f", frequency.overallGHz)
|
||||
}
|
||||
|
||||
private var subtitle: String? {
|
||||
guard let frequency else { return nil }
|
||||
var components: [String] = []
|
||||
if let eValue = frequency.eCoreGHz {
|
||||
components.append("E \(String(format: "%.1f", eValue))")
|
||||
}
|
||||
if let pValue = frequency.pCoreGHz {
|
||||
components.append("P \(String(format: "%.1f", pValue))")
|
||||
}
|
||||
return components.isEmpty ? nil : components.joined(separator: " • ")
|
||||
}
|
||||
}
|
||||
|
||||
private struct CPUSegmentDonut: View {
|
||||
let breakdown: CPULoadBreakdown
|
||||
let systemColor: Color
|
||||
|
|
|
|||
|
|
@ -379,6 +379,8 @@ class StatsManager: ObservableObject {
|
|||
@Published private(set) var cpuCoreUsage: [CPUCoreUsage] = []
|
||||
@Published private(set) var cpuUptime: TimeInterval = 0
|
||||
@Published private(set) var topCPUProcesses: [ProcessStats] = []
|
||||
@Published private(set) var cpuTemperature: CPUTemperatureMetrics = CPUTemperatureMetrics(celsius: nil)
|
||||
@Published private(set) var cpuFrequency: CPUFrequencyMetrics?
|
||||
@Published private(set) var memoryBreakdown: MemoryBreakdown = .zero
|
||||
@Published private(set) var gpuBreakdown: GPUBreakdown = .zero
|
||||
@Published private(set) var gpuDevices: [GPUDeviceMetrics] = []
|
||||
|
|
@ -439,6 +441,7 @@ class StatsManager: ObservableObject {
|
|||
private let maxProcessEntries: Int = 20
|
||||
private var isProcessRefreshInFlight = false
|
||||
private let gpuCollector = GPUInfoCollector()
|
||||
private let cpuSensorCollector = CPUSensorCollector()
|
||||
|
||||
// MARK: - Initialization
|
||||
private init() {
|
||||
|
|
@ -562,6 +565,8 @@ class StatsManager: ObservableObject {
|
|||
diskDevices = []
|
||||
previousInterfaceCounters.removeAll()
|
||||
interfaceTotals.removeAll()
|
||||
cpuTemperature = CPUTemperatureMetrics(celsius: nil)
|
||||
cpuFrequency = nil
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
|
@ -656,6 +661,10 @@ class StatsManager: ObservableObject {
|
|||
cpuCoreUsage = coreUsage
|
||||
}
|
||||
cpuUptime = ProcessInfo.processInfo.systemUptime
|
||||
cpuTemperature = cpuSensorCollector.readTemperature()
|
||||
if let frequencyMetrics = cpuSensorCollector.readFrequency() {
|
||||
cpuFrequency = frequencyMetrics
|
||||
}
|
||||
|
||||
// Update history arrays (sliding window)
|
||||
updateHistory(value: newCpuUsage, history: &cpuHistory)
|
||||
|
|
|
|||
162
DynamicIsland/utils/AppleHardwareInfo.swift
Normal file
162
DynamicIsland/utils/AppleHardwareInfo.swift
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// AppleHardwareInfo.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Minimal system introspection helpers derived from the Stats project.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IOKit
|
||||
import Darwin
|
||||
|
||||
enum ApplePlatform: String {
|
||||
case intel
|
||||
case m1
|
||||
case m1Pro
|
||||
case m1Max
|
||||
case m1Ultra
|
||||
case m2
|
||||
case m2Pro
|
||||
case m2Max
|
||||
case m2Ultra
|
||||
case m3
|
||||
case m3Pro
|
||||
case m3Max
|
||||
case m3Ultra
|
||||
case m4
|
||||
case m4Pro
|
||||
case m4Max
|
||||
case m4Ultra
|
||||
}
|
||||
|
||||
struct CPUClusterFrequencies {
|
||||
let eCoreFrequencies: [Int32]
|
||||
let pCoreFrequencies: [Int32]
|
||||
}
|
||||
|
||||
struct CPUClusterCounts {
|
||||
let eCores: Int
|
||||
let pCores: Int
|
||||
}
|
||||
|
||||
final class AppleHardwareInfo {
|
||||
static let shared = AppleHardwareInfo()
|
||||
let cpuName: String?
|
||||
let platform: ApplePlatform?
|
||||
let logicalCoreCount: Int
|
||||
let clusterCounts: CPUClusterCounts
|
||||
let clusterFrequencies: CPUClusterFrequencies
|
||||
|
||||
private init() {
|
||||
let name = AppleHardwareInfo.fetchCPUName()
|
||||
cpuName = name
|
||||
platform = AppleHardwareInfo.detectPlatform(from: name)
|
||||
logicalCoreCount = AppleHardwareInfo.fetchLogicalCoreCount()
|
||||
clusterCounts = AppleHardwareInfo.fetchClusterCounts()
|
||||
clusterFrequencies = AppleHardwareInfo.fetchClusterFrequencies(cpuName: name ?? "")
|
||||
}
|
||||
|
||||
private static func fetchCPUName() -> String? {
|
||||
var size = 0
|
||||
sysctlbyname("machdep.cpu.brand_string", nil, &size, nil, 0)
|
||||
guard size > 0 else { return nil }
|
||||
var buffer = [CChar](repeating: 0, count: size)
|
||||
let result = sysctlbyname("machdep.cpu.brand_string", &buffer, &size, nil, 0)
|
||||
guard result == 0 else { return nil }
|
||||
return String(cString: buffer)
|
||||
}
|
||||
|
||||
private static func fetchLogicalCoreCount() -> Int {
|
||||
var cores: Int32 = 0
|
||||
var size = MemoryLayout<Int32>.size
|
||||
let result = sysctlbyname("hw.logicalcpu", &cores, &size, nil, 0)
|
||||
return result == 0 ? Int(cores) : ProcessInfo.processInfo.processorCount
|
||||
}
|
||||
|
||||
private static func detectPlatform(from cpuName: String?) -> ApplePlatform? {
|
||||
guard let name = cpuName?.lowercased() else { return nil }
|
||||
if name.contains("intel") { return .intel }
|
||||
if name.contains("m1") {
|
||||
if name.contains("ultra") { return .m1Ultra }
|
||||
if name.contains("max") { return .m1Max }
|
||||
if name.contains("pro") { return .m1Pro }
|
||||
return .m1
|
||||
}
|
||||
if name.contains("m2") {
|
||||
if name.contains("ultra") { return .m2Ultra }
|
||||
if name.contains("max") { return .m2Max }
|
||||
if name.contains("pro") { return .m2Pro }
|
||||
return .m2
|
||||
}
|
||||
if name.contains("m3") {
|
||||
if name.contains("ultra") { return .m3Ultra }
|
||||
if name.contains("max") { return .m3Max }
|
||||
if name.contains("pro") { return .m3Pro }
|
||||
return .m3
|
||||
}
|
||||
if name.contains("m4") {
|
||||
if name.contains("ultra") { return .m4Ultra }
|
||||
if name.contains("max") { return .m4Max }
|
||||
if name.contains("pro") { return .m4Pro }
|
||||
return .m4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func fetchClusterCounts() -> CPUClusterCounts {
|
||||
var iterator = io_iterator_t()
|
||||
guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMPE"), &iterator) == KERN_SUCCESS else {
|
||||
return CPUClusterCounts(eCores: 0, pCores: 0)
|
||||
}
|
||||
defer { IOObjectRelease(iterator) }
|
||||
var eCoreCount: Int32 = 0
|
||||
var pCoreCount: Int32 = 0
|
||||
|
||||
while case let service = IOIteratorNext(iterator), service != 0 {
|
||||
defer { IOObjectRelease(service) }
|
||||
var children = io_iterator_t()
|
||||
guard IORegistryEntryGetChildIterator(service, kIOServicePlane, &children) == KERN_SUCCESS else { continue }
|
||||
defer { IOObjectRelease(children) }
|
||||
|
||||
while case let child = IOIteratorNext(children), child != 0 {
|
||||
defer { IOObjectRelease(child) }
|
||||
guard let name = di_getIOName(child), name == "cpus", let props = di_getIOProperties(child) else { continue }
|
||||
if let data = props.object(forKey: "e-core-count") as? Data {
|
||||
eCoreCount = data.withUnsafeBytes { $0.load(as: Int32.self) }
|
||||
}
|
||||
if let data = props.object(forKey: "p-core-count") as? Data {
|
||||
pCoreCount = data.withUnsafeBytes { $0.load(as: Int32.self) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CPUClusterCounts(eCores: Int(eCoreCount), pCores: Int(pCoreCount))
|
||||
}
|
||||
|
||||
private static func fetchClusterFrequencies(cpuName: String) -> CPUClusterFrequencies {
|
||||
var iterator = io_iterator_t()
|
||||
guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMIODevice"), &iterator) == KERN_SUCCESS else {
|
||||
return CPUClusterFrequencies(eCoreFrequencies: [], pCoreFrequencies: [])
|
||||
}
|
||||
defer { IOObjectRelease(iterator) }
|
||||
|
||||
let lowerName = cpuName.lowercased()
|
||||
let isM4 = lowerName.contains("m4")
|
||||
var eFreq: [Int32] = []
|
||||
var pFreq: [Int32] = []
|
||||
|
||||
while case let service = IOIteratorNext(iterator), service != 0 {
|
||||
defer { IOObjectRelease(service) }
|
||||
guard let name = di_getIOName(service), name == "pmgr", let props = di_getIOProperties(service) else { continue }
|
||||
|
||||
if let data = props["voltage-states1-sram"] as? Data {
|
||||
eFreq = di_convertCFDataToFrequencies(data, isM4: isM4)
|
||||
}
|
||||
if let data = props["voltage-states5-sram"] as? Data {
|
||||
pFreq = di_convertCFDataToFrequencies(data, isM4: isM4)
|
||||
}
|
||||
}
|
||||
|
||||
return CPUClusterFrequencies(eCoreFrequencies: eFreq, pCoreFrequencies: pFreq)
|
||||
}
|
||||
}
|
||||
237
DynamicIsland/utils/CPUSensorCollector.swift
Normal file
237
DynamicIsland/utils/CPUSensorCollector.swift
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
//
|
||||
// CPUSensorCollector.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Ports the Stats project's CPU temperature and frequency readers.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IOKit
|
||||
|
||||
struct CPUFrequencyMetrics: Equatable {
|
||||
let overallGHz: Double
|
||||
let eCoreGHz: Double?
|
||||
let pCoreGHz: Double?
|
||||
let maxOverallGHz: Double
|
||||
let maxECoreGHz: Double?
|
||||
let maxPCoreGHz: Double?
|
||||
}
|
||||
|
||||
struct CPUTemperatureMetrics: Equatable {
|
||||
let celsius: Double?
|
||||
}
|
||||
|
||||
final class CPUSensorCollector {
|
||||
private let hardware = AppleHardwareInfo.shared
|
||||
private var channels: CFMutableDictionary?
|
||||
private var subscription: IOReportSubscriptionRef?
|
||||
private var previousSample: (samples: CFDictionary, time: TimeInterval)?
|
||||
|
||||
init() {
|
||||
setupFrequencyChannel()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let subscription {
|
||||
IOReportCreateSamples(subscription, nil, nil) // best-effort flush
|
||||
}
|
||||
}
|
||||
|
||||
func readTemperature() -> CPUTemperatureMetrics {
|
||||
let platform = hardware.platform
|
||||
if let value = primaryTemperatureKeyCandidates.compactMap({ SMC.shared.getValue($0) }).first(where: { $0 < 110 }) {
|
||||
return CPUTemperatureMetrics(celsius: value)
|
||||
}
|
||||
let list = temperatureFallbackKeys(for: platform)
|
||||
var total: Double = 0
|
||||
var count: Double = 0
|
||||
for key in list {
|
||||
if let value = SMC.shared.getValue(key), value < 110 {
|
||||
total += value
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
guard total > 0, count > 0 else {
|
||||
return CPUTemperatureMetrics(celsius: nil)
|
||||
}
|
||||
return CPUTemperatureMetrics(celsius: total / count)
|
||||
}
|
||||
|
||||
func readFrequency() -> CPUFrequencyMetrics? {
|
||||
guard let channels, let subscription else { return nil }
|
||||
let timestamp = Date().timeIntervalSince1970
|
||||
guard let currentSample = IOReportCreateSamples(subscription, channels, nil)?.takeRetainedValue() else {
|
||||
return nil
|
||||
}
|
||||
defer {
|
||||
previousSample = (currentSample, timestamp)
|
||||
}
|
||||
guard let previous = previousSample,
|
||||
let diff = IOReportCreateSamplesDelta(previous.samples, currentSample, nil)?.takeRetainedValue() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let samples = collectIOSamples(from: diff)
|
||||
let eFrequencies = hardware.clusterFrequencies.eCoreFrequencies
|
||||
let pFrequencies = hardware.clusterFrequencies.pCoreFrequencies
|
||||
let eCount = max(hardware.clusterCounts.eCores, 0)
|
||||
let pCount = max(hardware.clusterCounts.pCores, 0)
|
||||
|
||||
var eValues: [Double] = []
|
||||
var pValues: [Double] = []
|
||||
|
||||
for sample in samples where sample.group == "CPU Stats" {
|
||||
if sample.channel.hasPrefix("ECPU") {
|
||||
let value = calculateFrequency(from: sample.delta, freqs: eFrequencies)
|
||||
if value > 0 { eValues.append(value) }
|
||||
} else if sample.channel.hasPrefix("PCPU") {
|
||||
let value = calculateFrequency(from: sample.delta, freqs: pFrequencies)
|
||||
if value > 0 { pValues.append(value) }
|
||||
}
|
||||
}
|
||||
|
||||
let eAverage = eValues.isEmpty ? nil : (eValues.reduce(0, +) / Double(eValues.count))
|
||||
let pAverage = pValues.isEmpty ? nil : (pValues.reduce(0, +) / Double(pValues.count))
|
||||
|
||||
guard eAverage != nil || pAverage != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let overall: Double
|
||||
if let eAverage, let pAverage, (eCount + pCount) > 0 {
|
||||
overall = ((eAverage * Double(eCount)) + (pAverage * Double(pCount))) / Double(eCount + pCount)
|
||||
} else if let eAverage {
|
||||
overall = eAverage
|
||||
} else if let pAverage {
|
||||
overall = pAverage
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let maxE = eFrequencies.max().map { Double($0) / 1000 }
|
||||
let maxP = pFrequencies.max().map { Double($0) / 1000 }
|
||||
let maxOverall: Double
|
||||
if let maxE, let maxP, (eCount + pCount) > 0 {
|
||||
maxOverall = ((maxE * Double(eCount)) + (maxP * Double(pCount))) / Double(max(eCount + pCount, 1))
|
||||
} else if let maxE {
|
||||
maxOverall = maxE
|
||||
} else if let maxP {
|
||||
maxOverall = maxP
|
||||
} else {
|
||||
maxOverall = (overall / 1000)
|
||||
}
|
||||
|
||||
return CPUFrequencyMetrics(
|
||||
overallGHz: overall / 1000,
|
||||
eCoreGHz: eAverage.map { $0 / 1000 },
|
||||
pCoreGHz: pAverage.map { $0 / 1000 },
|
||||
maxOverallGHz: maxOverall,
|
||||
maxECoreGHz: maxE,
|
||||
maxPCoreGHz: maxP
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private func setupFrequencyChannel() {
|
||||
let names = [
|
||||
("CPU Stats" as CFString, "CPU Complex Performance States" as CFString),
|
||||
("CPU Stats" as CFString, "CPU Core Performance States" as CFString)
|
||||
]
|
||||
var mergedChannels: CFMutableDictionary?
|
||||
var list: [CFDictionary] = []
|
||||
for (group, subgroup) in names {
|
||||
if let channel = IOReportCopyChannelsInGroup(group, subgroup, 0, 0, 0)?.takeRetainedValue() {
|
||||
list.append(channel)
|
||||
}
|
||||
}
|
||||
guard let first = list.first else { return }
|
||||
for channel in list.dropFirst() {
|
||||
IOReportMergeChannels(first, channel, nil)
|
||||
}
|
||||
guard let copy = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(first), first) else {
|
||||
return
|
||||
}
|
||||
mergedChannels = copy
|
||||
channels = mergedChannels
|
||||
var dictionary: Unmanaged<CFMutableDictionary>?
|
||||
subscription = IOReportCreateSubscription(nil, mergedChannels, &dictionary, 0, nil)
|
||||
dictionary?.release()
|
||||
}
|
||||
|
||||
private struct IOSample {
|
||||
let group: String
|
||||
let subGroup: String
|
||||
let channel: String
|
||||
let unit: String
|
||||
let delta: CFDictionary
|
||||
}
|
||||
|
||||
private func collectIOSamples(from data: CFDictionary) -> [IOSample] {
|
||||
let key = "IOReportChannels" as CFString
|
||||
var rawValue: UnsafeRawPointer?
|
||||
let found = withUnsafeMutablePointer(to: &rawValue) { pointer -> Bool in
|
||||
CFDictionaryGetValueIfPresent(data, Unmanaged.passUnretained(key).toOpaque(), pointer)
|
||||
}
|
||||
guard found, let rawValue else { return [] }
|
||||
let array = unsafeBitCast(rawValue, to: CFArray.self)
|
||||
var result: [IOSample] = []
|
||||
for index in 0..<CFArrayGetCount(array) {
|
||||
let element = CFArrayGetValueAtIndex(array, index)
|
||||
let entry = unsafeBitCast(element, to: CFDictionary.self)
|
||||
let group = IOReportChannelGetGroup(entry)?.takeUnretainedValue() as String? ?? ""
|
||||
let subGroup = IOReportChannelGetSubGroup(entry)?.takeUnretainedValue() as String? ?? ""
|
||||
let channel = IOReportChannelGetChannelName(entry)?.takeUnretainedValue() as String? ?? ""
|
||||
let unit = IOReportChannelGetUnitLabel(entry)?.takeUnretainedValue() as String? ?? ""
|
||||
result.append(IOSample(group: group, subGroup: subGroup, channel: channel, unit: unit, delta: entry))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func getResidencies(dict: CFDictionary) -> [(label: String, value: Int64)] {
|
||||
let count = IOReportStateGetCount(dict)
|
||||
var result: [(String, Int64)] = []
|
||||
for index in 0..<count {
|
||||
let name = IOReportStateGetNameForIndex(dict, index)?.takeUnretainedValue() as String? ?? ""
|
||||
let value = IOReportStateGetResidency(dict, index)
|
||||
result.append((name, value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func calculateFrequency(from dict: CFDictionary, freqs: [Int32]) -> Double {
|
||||
guard !freqs.isEmpty else { return 0 }
|
||||
let states = getResidencies(dict: dict)
|
||||
guard let offset = states.firstIndex(where: { !$0.label.elementsEqual("IDLE") && !$0.label.elementsEqual("DOWN") && !$0.label.elementsEqual("OFF") }) else {
|
||||
return 0
|
||||
}
|
||||
let usage = states.dropFirst(offset).reduce(0.0) { $0 + Double($1.value) }
|
||||
guard usage > 0 else { return 0 }
|
||||
var total: Double = 0
|
||||
for (index, frequency) in freqs.enumerated() {
|
||||
let stateIndex = offset + index
|
||||
guard states.indices.contains(stateIndex) else { continue }
|
||||
let fraction = Double(states[stateIndex].value) / usage
|
||||
total += fraction * Double(frequency)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
private var primaryTemperatureKeyCandidates: [String] {
|
||||
["TC0D", "TC0E", "TC0F", "TC0P", "TC0H"]
|
||||
}
|
||||
|
||||
private func temperatureFallbackKeys(for platform: ApplePlatform?) -> [String] {
|
||||
switch platform {
|
||||
case .m1, .m1Pro, .m1Max, .m1Ultra:
|
||||
return ["Tp09", "Tp0T", "Tp01", "Tp05", "Tp0D", "Tp0H", "Tp0L", "Tp0P", "Tp0X", "Tp0b"]
|
||||
case .m2, .m2Pro, .m2Max, .m2Ultra:
|
||||
return ["Tp1h", "Tp1t", "Tp1p", "Tp1l", "Tp01", "Tp05", "Tp09", "Tp0D", "Tp0X", "Tp0b", "Tp0f", "Tp0j"]
|
||||
case .m3, .m3Pro, .m3Max, .m3Ultra:
|
||||
return ["Te05", "Te0L", "Te0P", "Te0S", "Tf04", "Tf09", "Tf0A", "Tf0B", "Tf0D", "Tf0E", "Tf44", "Tf49", "Tf4A", "Tf4B", "Tf4D", "Tf4E"]
|
||||
case .m4, .m4Pro, .m4Max, .m4Ultra:
|
||||
return ["Te05", "Te09", "Te0H", "Te0S", "Tp01", "Tp05", "Tp09", "Tp0D", "Tp0V", "Tp0Y", "Tp0b", "Tp0e"]
|
||||
default:
|
||||
return ["TC0P", "TC0E", "TC0F", "TC0H", "TC0D"]
|
||||
}
|
||||
}
|
||||
}
|
||||
52
DynamicIsland/utils/IOKitHelpers.swift
Normal file
52
DynamicIsland/utils/IOKitHelpers.swift
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// IOKitHelpers.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Helper utilities borrowed from the Stats project for working with IORegistry and CFData blobs.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IOKit
|
||||
import Darwin
|
||||
|
||||
func di_getIOProperties(_ entry: io_registry_entry_t) -> NSDictionary? {
|
||||
var properties: Unmanaged<CFMutableDictionary>? = nil
|
||||
guard IORegistryEntryCreateCFProperties(entry, &properties, kCFAllocatorDefault, 0) == kIOReturnSuccess else {
|
||||
return nil
|
||||
}
|
||||
defer { properties?.release() }
|
||||
return properties?.takeUnretainedValue()
|
||||
}
|
||||
|
||||
func di_getIOName(_ entry: io_registry_entry_t) -> String? {
|
||||
let pointer = UnsafeMutablePointer<io_name_t>.allocate(capacity: 1)
|
||||
defer { pointer.deallocate() }
|
||||
let result = IORegistryEntryGetName(entry, pointer)
|
||||
guard result == kIOReturnSuccess else {
|
||||
if let message = String(validatingUTF8: mach_error_string(result)) {
|
||||
print("IORegistryEntryGetName error: \(message)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return String(cString: UnsafeRawPointer(pointer).assumingMemoryBound(to: CChar.self))
|
||||
}
|
||||
|
||||
func di_convertCFDataToFrequencies(_ data: Data, isM4: Bool = false) -> [Int32] {
|
||||
guard !data.isEmpty else { return [] }
|
||||
let bytes = [UInt8](data)
|
||||
|
||||
let multiplier: UInt32 = isM4 ? 1_000 : 1_000_000
|
||||
var frequencies: [Int32] = []
|
||||
|
||||
for chunkStart in stride(from: 0, to: bytes.count, by: 8) {
|
||||
let upperBound = min(chunkStart + 8, bytes.count)
|
||||
let chunk = bytes[chunkStart..<upperBound]
|
||||
if chunk.count < 4 { continue }
|
||||
let value = chunk.prefix(4).enumerated().reduce(UInt32(0)) { partial, element in
|
||||
partial | (UInt32(element.element) << (UInt32(element.offset) * 8))
|
||||
}
|
||||
frequencies.append(Int32(value / multiplier))
|
||||
}
|
||||
|
||||
return frequencies
|
||||
}
|
||||
112
DynamicIsland/utils/IOReportBridging.swift
Normal file
112
DynamicIsland/utils/IOReportBridging.swift
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//
|
||||
// IOReportBridging.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Lightweight runtime loader for private IOReport symbols used by the GPU/CPU collectors.
|
||||
// Avoids static linking against the private framework by resolving symbols via dlopen/dlsym.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Darwin
|
||||
|
||||
public typealias IOReportSubscriptionRef = OpaquePointer
|
||||
|
||||
private enum IOReportLoader {
|
||||
private static let libraryHandle: UnsafeMutableRawPointer? = {
|
||||
let paths = [
|
||||
"/System/Library/PrivateFrameworks/IOReport.framework/IOReport",
|
||||
"/usr/lib/libIOReport.dylib"
|
||||
]
|
||||
for path in paths {
|
||||
if let handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL) {
|
||||
return handle
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
static func symbol<T>(_ name: String, as type: T.Type) -> T? {
|
||||
guard let handle = libraryHandle, let pointer = dlsym(handle, name) else {
|
||||
return nil
|
||||
}
|
||||
return unsafeBitCast(pointer, to: T.self)
|
||||
}
|
||||
}
|
||||
|
||||
func IOReportCopyChannelsInGroup(_ group: CFString?, _ subGroup: CFString?, _ a: UInt64, _ b: UInt64, _ c: UInt64) -> Unmanaged<CFDictionary>? {
|
||||
typealias Fn = @convention(c) (CFString?, CFString?, UInt64, UInt64, UInt64) -> Unmanaged<CFDictionary>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportCopyChannelsInGroup", as: Fn.self) else { return nil }
|
||||
return fn(group, subGroup, a, b, c)
|
||||
}
|
||||
|
||||
func IOReportMergeChannels(_ a: CFDictionary, _ b: CFDictionary, _ c: CFTypeRef?) {
|
||||
typealias Fn = @convention(c) (CFDictionary, CFDictionary, CFTypeRef?) -> Void
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportMergeChannels", as: Fn.self) else { return }
|
||||
fn(a, b, c)
|
||||
}
|
||||
|
||||
func IOReportCreateSubscription(_ a: UnsafeMutableRawPointer?, _ b: CFMutableDictionary?, _ c: UnsafeMutablePointer<Unmanaged<CFMutableDictionary>?>?, _ d: UInt64, _ e: CFTypeRef?) -> IOReportSubscriptionRef? {
|
||||
typealias Fn = @convention(c) (UnsafeMutableRawPointer?, CFMutableDictionary?, UnsafeMutablePointer<Unmanaged<CFMutableDictionary>?>?, UInt64, CFTypeRef?) -> IOReportSubscriptionRef?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportCreateSubscription", as: Fn.self) else { return nil }
|
||||
return fn(a, b, c, d, e)
|
||||
}
|
||||
|
||||
func IOReportCreateSamples(_ subscription: IOReportSubscriptionRef?, _ channels: CFMutableDictionary?, _ client: CFTypeRef?) -> Unmanaged<CFDictionary>? {
|
||||
typealias Fn = @convention(c) (IOReportSubscriptionRef?, CFMutableDictionary?, CFTypeRef?) -> Unmanaged<CFDictionary>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportCreateSamples", as: Fn.self) else { return nil }
|
||||
return fn(subscription, channels, client)
|
||||
}
|
||||
|
||||
func IOReportCreateSamplesDelta(_ previous: CFDictionary, _ current: CFDictionary, _ zone: CFTypeRef?) -> Unmanaged<CFDictionary>? {
|
||||
typealias Fn = @convention(c) (CFDictionary, CFDictionary, CFTypeRef?) -> Unmanaged<CFDictionary>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportCreateSamplesDelta", as: Fn.self) else { return nil }
|
||||
return fn(previous, current, zone)
|
||||
}
|
||||
|
||||
func IOReportChannelGetGroup(_ dictionary: CFDictionary) -> Unmanaged<CFString>? {
|
||||
typealias Fn = @convention(c) (CFDictionary) -> Unmanaged<CFString>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportChannelGetGroup", as: Fn.self) else { return nil }
|
||||
return fn(dictionary)
|
||||
}
|
||||
|
||||
func IOReportChannelGetSubGroup(_ dictionary: CFDictionary) -> Unmanaged<CFString>? {
|
||||
typealias Fn = @convention(c) (CFDictionary) -> Unmanaged<CFString>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportChannelGetSubGroup", as: Fn.self) else { return nil }
|
||||
return fn(dictionary)
|
||||
}
|
||||
|
||||
func IOReportChannelGetChannelName(_ dictionary: CFDictionary) -> Unmanaged<CFString>? {
|
||||
typealias Fn = @convention(c) (CFDictionary) -> Unmanaged<CFString>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportChannelGetChannelName", as: Fn.self) else { return nil }
|
||||
return fn(dictionary)
|
||||
}
|
||||
|
||||
func IOReportChannelGetUnitLabel(_ dictionary: CFDictionary) -> Unmanaged<CFString>? {
|
||||
typealias Fn = @convention(c) (CFDictionary) -> Unmanaged<CFString>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportChannelGetUnitLabel", as: Fn.self) else { return nil }
|
||||
return fn(dictionary)
|
||||
}
|
||||
|
||||
func IOReportStateGetCount(_ dictionary: CFDictionary) -> Int32 {
|
||||
typealias Fn = @convention(c) (CFDictionary) -> Int32
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportStateGetCount", as: Fn.self) else { return 0 }
|
||||
return fn(dictionary)
|
||||
}
|
||||
|
||||
func IOReportStateGetNameForIndex(_ dictionary: CFDictionary, _ index: Int32) -> Unmanaged<CFString>? {
|
||||
typealias Fn = @convention(c) (CFDictionary, Int32) -> Unmanaged<CFString>?
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportStateGetNameForIndex", as: Fn.self) else { return nil }
|
||||
return fn(dictionary, index)
|
||||
}
|
||||
|
||||
func IOReportStateGetResidency(_ dictionary: CFDictionary, _ index: Int32) -> Int64 {
|
||||
typealias Fn = @convention(c) (CFDictionary, Int32) -> Int64
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportStateGetResidency", as: Fn.self) else { return 0 }
|
||||
return fn(dictionary, index)
|
||||
}
|
||||
|
||||
func IOReportSimpleGetIntegerValue(_ dictionary: CFDictionary, _ index: Int32) -> Int64 {
|
||||
typealias Fn = @convention(c) (CFDictionary, Int32) -> Int64
|
||||
guard let fn: Fn = IOReportLoader.symbol("IOReportSimpleGetIntegerValue", as: Fn.self) else { return 0 }
|
||||
return fn(dictionary, index)
|
||||
}
|
||||
532
DynamicIsland/utils/SMC.swift
Normal file
532
DynamicIsland/utils/SMC.swift
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
//
|
||||
// smc.swift
|
||||
// SMC
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 25/05/2021.
|
||||
// Using Swift 5.0.
|
||||
// Running on macOS 10.15.
|
||||
//
|
||||
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IOKit
|
||||
|
||||
internal enum SMCDataType: String {
|
||||
case UI8 = "ui8 "
|
||||
case UI16 = "ui16"
|
||||
case UI32 = "ui32"
|
||||
case SP1E = "sp1e"
|
||||
case SP3C = "sp3c"
|
||||
case SP4B = "sp5b"
|
||||
case SP5A = "sp5a"
|
||||
case SPA5 = "spa5"
|
||||
case SP69 = "sp669"
|
||||
case SP78 = "sp78"
|
||||
case SP87 = "sp87"
|
||||
case SP96 = "sp96"
|
||||
case SPB4 = "spb4"
|
||||
case SPF0 = "spf0"
|
||||
case FLT = "flt "
|
||||
case FPE2 = "fpe2"
|
||||
case FP2E = "fp2e"
|
||||
case FDS = "{fds"
|
||||
}
|
||||
|
||||
internal enum SMCKeys: UInt8 {
|
||||
case kernelIndex = 2
|
||||
case readBytes = 5
|
||||
case writeBytes = 6
|
||||
case readIndex = 8
|
||||
case readKeyInfo = 9
|
||||
case readPLimit = 11
|
||||
case readVers = 12
|
||||
}
|
||||
|
||||
public enum FanMode: Int, Codable {
|
||||
case automatic = 0
|
||||
case forced = 1
|
||||
}
|
||||
|
||||
internal struct SMCKeyData_t {
|
||||
typealias SMCBytes_t = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8)
|
||||
|
||||
struct vers_t {
|
||||
var major: CUnsignedChar = 0
|
||||
var minor: CUnsignedChar = 0
|
||||
var build: CUnsignedChar = 0
|
||||
var reserved: CUnsignedChar = 0
|
||||
var release: CUnsignedShort = 0
|
||||
}
|
||||
|
||||
struct LimitData_t {
|
||||
var version: UInt16 = 0
|
||||
var length: UInt16 = 0
|
||||
var cpuPLimit: UInt32 = 0
|
||||
var gpuPLimit: UInt32 = 0
|
||||
var memPLimit: UInt32 = 0
|
||||
}
|
||||
|
||||
struct keyInfo_t {
|
||||
var dataSize: IOByteCount32 = 0
|
||||
var dataType: UInt32 = 0
|
||||
var dataAttributes: UInt8 = 0
|
||||
}
|
||||
|
||||
var key: UInt32 = 0
|
||||
var vers = vers_t()
|
||||
var pLimitData = LimitData_t()
|
||||
var keyInfo = keyInfo_t()
|
||||
var padding: UInt16 = 0
|
||||
var result: UInt8 = 0
|
||||
var status: UInt8 = 0
|
||||
var data8: UInt8 = 0
|
||||
var data32: UInt32 = 0
|
||||
var bytes: SMCBytes_t = (UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0))
|
||||
}
|
||||
|
||||
internal struct SMCVal_t {
|
||||
var key: String
|
||||
var dataSize: UInt32 = 0
|
||||
var dataType: String = ""
|
||||
var bytes: [UInt8] = Array(repeating: 0, count: 32)
|
||||
|
||||
init(_ key: String) {
|
||||
self.key = key
|
||||
}
|
||||
}
|
||||
|
||||
extension FourCharCode {
|
||||
init(fromString str: String) {
|
||||
precondition(str.count == 4)
|
||||
|
||||
self = str.utf8.reduce(0) { sum, character in
|
||||
return sum << 8 | UInt32(character)
|
||||
}
|
||||
}
|
||||
|
||||
func toString() -> String {
|
||||
return String(describing: UnicodeScalar(self >> 24 & 0xff)!) +
|
||||
String(describing: UnicodeScalar(self >> 16 & 0xff)!) +
|
||||
String(describing: UnicodeScalar(self >> 8 & 0xff)!) +
|
||||
String(describing: UnicodeScalar(self & 0xff)!)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt16 {
|
||||
init(bytes: (UInt8, UInt8)) {
|
||||
self = UInt16(bytes.0) << 8 | UInt16(bytes.1)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt32 {
|
||||
init(bytes: (UInt8, UInt8, UInt8, UInt8)) {
|
||||
self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
init(fromFPE2 bytes: (UInt8, UInt8)) {
|
||||
self = (Int(bytes.0) << 6) + (Int(bytes.1) >> 2)
|
||||
}
|
||||
}
|
||||
|
||||
extension Float {
|
||||
init?(_ bytes: [UInt8]) {
|
||||
self = bytes.withUnsafeBytes {
|
||||
return $0.load(fromByteOffset: 0, as: Self.self)
|
||||
}
|
||||
}
|
||||
|
||||
var bytes: [UInt8] {
|
||||
withUnsafeBytes(of: self, Array.init)
|
||||
}
|
||||
}
|
||||
|
||||
public class SMC {
|
||||
public static let shared = SMC()
|
||||
private var conn: io_connect_t = 0
|
||||
|
||||
public init() {
|
||||
var result: kern_return_t
|
||||
var iterator: io_iterator_t = 0
|
||||
let device: io_object_t
|
||||
|
||||
let matchingDictionary: CFMutableDictionary = IOServiceMatching("AppleSMC")
|
||||
result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
|
||||
device = IOIteratorNext(iterator)
|
||||
IOObjectRelease(iterator)
|
||||
if device == 0 {
|
||||
print("Error IOIteratorNext(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
|
||||
result = IOServiceOpen(device, mach_task_self_, 0, &conn)
|
||||
IOObjectRelease(device)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error IOServiceOpen(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
let result = self.close()
|
||||
if result != kIOReturnSuccess {
|
||||
print("error close smc connection: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
}
|
||||
}
|
||||
|
||||
public func close() -> kern_return_t {
|
||||
return IOServiceClose(conn)
|
||||
}
|
||||
|
||||
public func getValue(_ key: String) -> Double? {
|
||||
var result: kern_return_t = 0
|
||||
var val: SMCVal_t = SMCVal_t(key)
|
||||
|
||||
result = read(&val)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error read(\(key)): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.dataSize > 0 {
|
||||
if val.bytes.first(where: { $0 != 0 }) == nil && val.key != "FS! " && val.key != "F0Md" && val.key != "F1Md" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch val.dataType {
|
||||
case SMCDataType.UI8.rawValue:
|
||||
return Double(val.bytes[0])
|
||||
case SMCDataType.UI16.rawValue:
|
||||
return Double(UInt16(bytes: (val.bytes[0], val.bytes[1])))
|
||||
case SMCDataType.UI32.rawValue:
|
||||
return Double(UInt32(bytes: (val.bytes[0], val.bytes[1], val.bytes[2], val.bytes[3])))
|
||||
case SMCDataType.SP1E.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 16384)
|
||||
case SMCDataType.SP3C.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 4096)
|
||||
case SMCDataType.SP4B.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 2048)
|
||||
case SMCDataType.SP5A.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 1024)
|
||||
case SMCDataType.SP69.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 512)
|
||||
case SMCDataType.SP78.rawValue:
|
||||
let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1]))
|
||||
return Double(intValue / 256)
|
||||
case SMCDataType.SP87.rawValue:
|
||||
let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1]))
|
||||
return Double(intValue / 128)
|
||||
case SMCDataType.SP96.rawValue:
|
||||
let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1]))
|
||||
return Double(intValue / 64)
|
||||
case SMCDataType.SPA5.rawValue:
|
||||
let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1]))
|
||||
return Double(result / 32)
|
||||
case SMCDataType.SPB4.rawValue:
|
||||
let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1]))
|
||||
return Double(intValue / 16)
|
||||
case SMCDataType.SPF0.rawValue:
|
||||
let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1]))
|
||||
return intValue
|
||||
case SMCDataType.FLT.rawValue:
|
||||
let value: Float? = Float(val.bytes)
|
||||
if value != nil {
|
||||
return Double(value!)
|
||||
}
|
||||
return nil
|
||||
case SMCDataType.FPE2.rawValue:
|
||||
return Double(Int(fromFPE2: (val.bytes[0], val.bytes[1])))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getStringValue(_ key: String) -> String? {
|
||||
var result: kern_return_t = 0
|
||||
var val: SMCVal_t = SMCVal_t(key)
|
||||
|
||||
result = read(&val)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error read(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.dataSize > 0 {
|
||||
if val.bytes.first(where: { $0 != 0}) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch val.dataType {
|
||||
case SMCDataType.FDS.rawValue:
|
||||
let c1 = String(UnicodeScalar(val.bytes[4]))
|
||||
let c2 = String(UnicodeScalar(val.bytes[5]))
|
||||
let c3 = String(UnicodeScalar(val.bytes[6]))
|
||||
let c4 = String(UnicodeScalar(val.bytes[7]))
|
||||
let c5 = String(UnicodeScalar(val.bytes[8]))
|
||||
let c6 = String(UnicodeScalar(val.bytes[9]))
|
||||
let c7 = String(UnicodeScalar(val.bytes[10]))
|
||||
let c8 = String(UnicodeScalar(val.bytes[11]))
|
||||
let c9 = String(UnicodeScalar(val.bytes[12]))
|
||||
let c10 = String(UnicodeScalar(val.bytes[13]))
|
||||
let c11 = String(UnicodeScalar(val.bytes[14]))
|
||||
let c12 = String(UnicodeScalar(val.bytes[15]))
|
||||
|
||||
return (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12).trimmingCharacters(in: .whitespaces)
|
||||
default:
|
||||
print("unsupported data type \(val.dataType) for key: \(key)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getAllKeys() -> [String] {
|
||||
var list: [String] = []
|
||||
|
||||
let keysNum: Double? = self.getValue("#KEY")
|
||||
if keysNum == nil {
|
||||
print("ERROR no keys count found")
|
||||
return list
|
||||
}
|
||||
|
||||
var result: kern_return_t = 0
|
||||
var input: SMCKeyData_t = SMCKeyData_t()
|
||||
var output: SMCKeyData_t = SMCKeyData_t()
|
||||
|
||||
for i in 0...Int(keysNum!) {
|
||||
input = SMCKeyData_t()
|
||||
output = SMCKeyData_t()
|
||||
|
||||
input.data8 = SMCKeys.readIndex.rawValue
|
||||
input.data32 = UInt32(i)
|
||||
|
||||
result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output)
|
||||
if result != kIOReturnSuccess {
|
||||
continue
|
||||
}
|
||||
|
||||
list.append(output.key.toString())
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
public func write(_ key: String, _ newValue: Int) -> kern_return_t {
|
||||
var value = SMCVal_t(key)
|
||||
value.dataSize = 2
|
||||
value.bytes = [UInt8(newValue >> 6), UInt8((newValue << 2) ^ ((newValue >> 6) << 8)), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0)]
|
||||
|
||||
return write(value)
|
||||
}
|
||||
|
||||
// MARK: - fans
|
||||
|
||||
public func setFanMode(_ id: Int, mode: FanMode) {
|
||||
if self.getValue("F\(id)Md") != nil {
|
||||
var result: kern_return_t = 0
|
||||
var value = SMCVal_t("F\(id)Md")
|
||||
|
||||
result = read(&value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error read fan mode: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
|
||||
value.bytes = [UInt8(mode.rawValue), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0)]
|
||||
|
||||
result = write(value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let fansMode = Int(self.getValue("FS! ") ?? 0)
|
||||
var newMode: UInt8 = 0
|
||||
|
||||
if fansMode == 0 && id == 0 && mode == .forced {
|
||||
newMode = 1
|
||||
} else if fansMode == 0 && id == 1 && mode == .forced {
|
||||
newMode = 2
|
||||
} else if fansMode == 1 && id == 0 && mode == .automatic {
|
||||
newMode = 0
|
||||
} else if fansMode == 1 && id == 1 && mode == .forced {
|
||||
newMode = 3
|
||||
} else if fansMode == 2 && id == 1 && mode == .automatic {
|
||||
newMode = 0
|
||||
} else if fansMode == 2 && id == 0 && mode == .forced {
|
||||
newMode = 3
|
||||
} else if fansMode == 3 && id == 0 && mode == .automatic {
|
||||
newMode = 2
|
||||
} else if fansMode == 3 && id == 1 && mode == .automatic {
|
||||
newMode = 1
|
||||
}
|
||||
|
||||
if fansMode == newMode {
|
||||
return
|
||||
}
|
||||
|
||||
var result: kern_return_t = 0
|
||||
var value = SMCVal_t("FS! ")
|
||||
|
||||
result = read(&value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error read fan mode: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
|
||||
value.bytes = [0, newMode, UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||
UInt8(0), UInt8(0)]
|
||||
|
||||
result = write(value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public func setFanSpeed(_ id: Int, speed: Int) {
|
||||
let maxSpeed = Int(self.getValue("F\(id)Mx") ?? 4000)
|
||||
|
||||
if speed > maxSpeed {
|
||||
print("new fan speed (\(speed)) is more than maximum speed (\(maxSpeed))")
|
||||
return
|
||||
}
|
||||
|
||||
var result: kern_return_t = 0
|
||||
var value = SMCVal_t("F\(id)Tg")
|
||||
|
||||
result = read(&value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error read fan value: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
|
||||
if value.dataType == "flt " {
|
||||
let bytes = Float(speed).bytes
|
||||
value.bytes[0] = bytes[0]
|
||||
value.bytes[1] = bytes[1]
|
||||
value.bytes[2] = bytes[2]
|
||||
value.bytes[3] = bytes[3]
|
||||
} else if value.dataType == "fpe2" {
|
||||
value.bytes[0] = UInt8(speed >> 6)
|
||||
value.bytes[1] = UInt8((speed << 2) ^ ((speed >> 6) << 8))
|
||||
value.bytes[2] = UInt8(0)
|
||||
value.bytes[3] = UInt8(0)
|
||||
}
|
||||
|
||||
result = write(value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public func resetFans() {
|
||||
var value = SMCVal_t("FS! ")
|
||||
value.dataSize = 2
|
||||
|
||||
let result = write(value)
|
||||
if result != kIOReturnSuccess {
|
||||
print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - internal functions
|
||||
|
||||
private func read(_ value: UnsafeMutablePointer<SMCVal_t>) -> kern_return_t {
|
||||
var result: kern_return_t = 0
|
||||
var input = SMCKeyData_t()
|
||||
var output = SMCKeyData_t()
|
||||
|
||||
input.key = FourCharCode(fromString: value.pointee.key)
|
||||
input.data8 = SMCKeys.readKeyInfo.rawValue
|
||||
|
||||
result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output)
|
||||
if result != kIOReturnSuccess {
|
||||
return result
|
||||
}
|
||||
|
||||
value.pointee.dataSize = UInt32(output.keyInfo.dataSize)
|
||||
value.pointee.dataType = output.keyInfo.dataType.toString()
|
||||
input.keyInfo.dataSize = output.keyInfo.dataSize
|
||||
input.data8 = SMCKeys.readBytes.rawValue
|
||||
|
||||
result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output)
|
||||
if result != kIOReturnSuccess {
|
||||
return result
|
||||
}
|
||||
|
||||
memcpy(&value.pointee.bytes, &output.bytes, Int(value.pointee.dataSize))
|
||||
|
||||
return kIOReturnSuccess
|
||||
}
|
||||
|
||||
private func write(_ value: SMCVal_t) -> kern_return_t {
|
||||
var input = SMCKeyData_t()
|
||||
var output = SMCKeyData_t()
|
||||
|
||||
input.key = FourCharCode(fromString: value.key)
|
||||
input.data8 = SMCKeys.writeBytes.rawValue
|
||||
input.keyInfo.dataSize = IOByteCount32(value.dataSize)
|
||||
input.bytes = (value.bytes[0], value.bytes[1], value.bytes[2], value.bytes[3], value.bytes[4], value.bytes[5],
|
||||
value.bytes[6], value.bytes[7], value.bytes[8], value.bytes[9], value.bytes[10], value.bytes[11],
|
||||
value.bytes[12], value.bytes[13], value.bytes[14], value.bytes[15], value.bytes[16], value.bytes[17],
|
||||
value.bytes[18], value.bytes[19], value.bytes[20], value.bytes[21], value.bytes[22], value.bytes[23],
|
||||
value.bytes[24], value.bytes[25], value.bytes[26], value.bytes[27], value.bytes[28], value.bytes[29],
|
||||
value.bytes[30], value.bytes[31])
|
||||
|
||||
let result = self.call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output)
|
||||
if result != kIOReturnSuccess {
|
||||
return result
|
||||
}
|
||||
|
||||
return kIOReturnSuccess
|
||||
}
|
||||
|
||||
private func call(_ index: UInt8, input: inout SMCKeyData_t, output: inout SMCKeyData_t) -> kern_return_t {
|
||||
let inputSize = MemoryLayout<SMCKeyData_t>.stride
|
||||
var outputSize = MemoryLayout<SMCKeyData_t>.stride
|
||||
|
||||
return IOConnectCallStructMethod(conn, UInt32(index), &input, inputSize, &output, &outputSize)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue