mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +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" : {
|
"Sensitivity" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Sensors" : {
|
||||||
|
"comment" : "A label displayed above the group of CPU sensor gauges.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Session · ↓ %@ · ↑ %@" : {
|
"Session · ↓ %@ · ↑ %@" : {
|
||||||
"comment" : "A row displaying the total downloaded and uploaded bytes for the network 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,
|
coreCount: statsManager.cpuCoreUsage.count,
|
||||||
systemColor: systemColor,
|
systemColor: systemColor,
|
||||||
userColor: userColor,
|
userColor: userColor,
|
||||||
idleColor: idleColor
|
idleColor: idleColor,
|
||||||
|
temperature: statsManager.cpuTemperature,
|
||||||
|
frequency: statsManager.cpuFrequency
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ struct CPUUsageDashboard: View {
|
||||||
let systemColor: Color
|
let systemColor: Color
|
||||||
let userColor: Color
|
let userColor: Color
|
||||||
let idleColor: Color
|
let idleColor: Color
|
||||||
|
let temperature: CPUTemperatureMetrics
|
||||||
|
let frequency: CPUFrequencyMetrics?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let usageSection = VStack(alignment: .leading, spacing: 12) {
|
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))
|
DetailRow(color: nil, label: "Load 15m", value: String(format: "%.2f", loadAverage.fifteenMinutes))
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.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) {
|
return ViewThatFits(in: .horizontal) {
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
|
|
@ -97,19 +107,23 @@ struct CPUUsageDashboard: View {
|
||||||
.frame(width: 132, height: 132)
|
.frame(width: 132, height: 132)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
sensorsSection
|
||||||
usageSection
|
usageSection
|
||||||
Divider().padding(.vertical, 4)
|
Divider().padding(.vertical, 4)
|
||||||
systemSection
|
systemSection
|
||||||
}
|
}
|
||||||
HStack(alignment: .top, spacing: 28) {
|
HStack(alignment: .top, spacing: 28) {
|
||||||
CPUSegmentDonut(
|
VStack(alignment: .leading, spacing: 18) {
|
||||||
breakdown: breakdown,
|
CPUSegmentDonut(
|
||||||
systemColor: systemColor,
|
breakdown: breakdown,
|
||||||
userColor: userColor,
|
systemColor: systemColor,
|
||||||
idleColor: idleColor
|
userColor: userColor,
|
||||||
)
|
idleColor: idleColor
|
||||||
.frame(width: 132, height: 132)
|
)
|
||||||
|
.frame(width: 132, height: 132)
|
||||||
|
|
||||||
|
sensorsSection
|
||||||
|
}
|
||||||
usageSection
|
usageSection
|
||||||
systemSection
|
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 {
|
private struct CPUSegmentDonut: View {
|
||||||
let breakdown: CPULoadBreakdown
|
let breakdown: CPULoadBreakdown
|
||||||
let systemColor: Color
|
let systemColor: Color
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,8 @@ class StatsManager: ObservableObject {
|
||||||
@Published private(set) var cpuCoreUsage: [CPUCoreUsage] = []
|
@Published private(set) var cpuCoreUsage: [CPUCoreUsage] = []
|
||||||
@Published private(set) var cpuUptime: TimeInterval = 0
|
@Published private(set) var cpuUptime: TimeInterval = 0
|
||||||
@Published private(set) var topCPUProcesses: [ProcessStats] = []
|
@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 memoryBreakdown: MemoryBreakdown = .zero
|
||||||
@Published private(set) var gpuBreakdown: GPUBreakdown = .zero
|
@Published private(set) var gpuBreakdown: GPUBreakdown = .zero
|
||||||
@Published private(set) var gpuDevices: [GPUDeviceMetrics] = []
|
@Published private(set) var gpuDevices: [GPUDeviceMetrics] = []
|
||||||
|
|
@ -439,6 +441,7 @@ class StatsManager: ObservableObject {
|
||||||
private let maxProcessEntries: Int = 20
|
private let maxProcessEntries: Int = 20
|
||||||
private var isProcessRefreshInFlight = false
|
private var isProcessRefreshInFlight = false
|
||||||
private let gpuCollector = GPUInfoCollector()
|
private let gpuCollector = GPUInfoCollector()
|
||||||
|
private let cpuSensorCollector = CPUSensorCollector()
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
private init() {
|
private init() {
|
||||||
|
|
@ -562,6 +565,8 @@ class StatsManager: ObservableObject {
|
||||||
diskDevices = []
|
diskDevices = []
|
||||||
previousInterfaceCounters.removeAll()
|
previousInterfaceCounters.removeAll()
|
||||||
interfaceTotals.removeAll()
|
interfaceTotals.removeAll()
|
||||||
|
cpuTemperature = CPUTemperatureMetrics(celsius: nil)
|
||||||
|
cpuFrequency = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
@ -656,6 +661,10 @@ class StatsManager: ObservableObject {
|
||||||
cpuCoreUsage = coreUsage
|
cpuCoreUsage = coreUsage
|
||||||
}
|
}
|
||||||
cpuUptime = ProcessInfo.processInfo.systemUptime
|
cpuUptime = ProcessInfo.processInfo.systemUptime
|
||||||
|
cpuTemperature = cpuSensorCollector.readTemperature()
|
||||||
|
if let frequencyMetrics = cpuSensorCollector.readFrequency() {
|
||||||
|
cpuFrequency = frequencyMetrics
|
||||||
|
}
|
||||||
|
|
||||||
// Update history arrays (sliding window)
|
// Update history arrays (sliding window)
|
||||||
updateHistory(value: newCpuUsage, history: &cpuHistory)
|
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