Files
macos-stats/SMC/main.swift
Alex Goodkind 20030a2a1c feat: added better fan control for M3/M4 Apple Silicon (#2924)
* Fix Ftst key handling for Apple Silicon fan control

* Update CFBundleVersion to 747 in Info.plist

Signed-off-by: Alex Goodkind <alex@goodkind.io>

* Update TeamId and SMC.Helper certificate identifier in Info.plist

Signed-off-by: Alex Goodkind <alex@goodkind.io>

* Add debug logging to SMC fan control functions

* Use writeWithRetry for Apple Silicon fan control writes and bump helper version to 1.0.3

* SMC fan control: serialize ops, Ftst timing, verification, logging

- Helper: serial queue for setFanMode/setFanSpeed/resetFanControl
- smc.swift: 3s wait after Ftst=1, longer mode retry (100ms), SMC result logging
- helpers: per-fan verification with cancel-on-supersede, clearer logs
- smc.swift: neutral write logs (no 'succeeded'), FAILED on error

* - Updated error handling in SMCHelper to suppress expected XPC errors (codes 4097 and 4099) during helper updates/restarts.
- Removed unnecessary debug print statement in ModeButtons for improved log clarity.

* Update version numbers in Info.plist files to 752 and change TeamId for SMC.Helper

* Add FanMode.auto3 and isAutomatic, re-add F%dMd write in automatic path, use isAutomatic in countManualFans; bump SMC Helper to 1.0.24

* Apple Silicon fan control: direct-first writes, strip diagnostic bloat

Try direct F%dMd=1 write before Ftst unlock (instant on M1, fallback
on M3/M4). Remove verification system, diagnostic prints, dead code.

* Apple Silicon fan control: direct-first writes

Try direct F%dMd=1 write before Ftst unlock (instant on M1,
fallback on M3/M4).

Bump helper to 1.0.2/3, Stats/Widgets to 751.

---------

Signed-off-by: Alex Goodkind <alex@goodkind.io>
2026-02-22 15:17:23 +01:00

171 lines
5.1 KiB
Swift

//
// main.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
enum CMDType: String {
case list
case set
case fan
case fans
case reset
case help
case unknown
init(value: String) {
switch value {
case "list": self = .list
case "set": self = .set
case "fan": self = .fan
case "fans": self = .fans
case "reset": self = .reset
case "help": self = .help
default: self = .unknown
}
}
}
enum FlagsType: String {
case temperature = "T"
case voltage = "V"
case power = "P"
case fans = "F"
case all
init(value: String) {
switch value {
case "-t": self = .temperature
case "-v": self = .voltage
case "-p": self = .power
case "-f": self = .fans
default: self = .all
}
}
}
func main() {
var args = CommandLine.arguments.dropFirst()
let cmd = CMDType(value: args.first ?? "")
args = args.dropFirst()
switch cmd {
case .list:
var keys = SMC.shared.getAllKeys()
args.forEach { (arg: String) in
let flag = FlagsType(value: arg)
if flag != .all {
keys = keys.filter{ $0.hasPrefix(flag.rawValue)}
}
}
print("[INFO]: found \(keys.count) keys\n")
keys.forEach { (key: String) in
let value = SMC.shared.getValue(key)
print("[\(key)] ", value ?? 0)
}
case .set:
guard let keyIndex = args.firstIndex(where: { $0 == "-k" }),
let valueIndex = args.firstIndex(where: { $0 == "-v" }),
args.indices.contains(keyIndex+1),
args.indices.contains(valueIndex+1) else {
return
}
let key = args[keyIndex+1]
if key.count != 4 {
print("[ERROR]: key must contain 4 characters!")
return
}
guard let value = Int(args[valueIndex+1]) else {
print("[ERROR]: wrong value passed!")
return
}
let result = SMC.shared.write(key, value)
if result != kIOReturnSuccess {
print("[ERROR]: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
return
}
print("[INFO]: set \(value) on \(key)")
case .fan:
guard let idString = args.first, let id = Int(idString) else {
print("[ERROR]: missing fan id")
return
}
var help: Bool = true
if let index = args.firstIndex(where: { $0 == "-v" }), args.indices.contains(index+1), let value = Int(args[index+1]) {
SMC.shared.setFanSpeed(id, speed: value)
help = false
}
if let index = args.firstIndex(where: { $0 == "-m" }), args.indices.contains(index+1),
let raw = Int(args[index+1]), let mode = FanMode.init(rawValue: raw) {
SMC.shared.setFanMode(id, mode: mode)
help = false
}
guard help else { return }
print("Available Flags:")
print(" -m change the fan mode: 0 - automatic, 1 - manual")
print(" -v change the fan speed")
case .fans:
guard let count = SMC.shared.getValue("FNum") else {
print("FNum not found")
return
}
print("Number of fans: \(count)\n")
for i in 0..<Int(count) {
print("\(i): \(SMC.shared.getStringValue("F\(i)ID") ?? "Fan #\(i)")")
print("Actual speed:", SMC.shared.getValue("F\(i)Ac") ?? -1)
print("Minimal speed:", SMC.shared.getValue("F\(i)Mn") ?? -1)
print("Maximum speed:", SMC.shared.getValue("F\(i)Mx") ?? -1)
print("Target speed:", SMC.shared.getValue("F\(i)Tg") ?? -1)
print("Mode:", FanMode(rawValue: Int(SMC.shared.getValue("F\(i)Md") ?? -1)) ?? .forced)
print()
}
case .reset:
#if arch(arm64)
if SMC.shared.resetFanControl() {
print("[reset] Ftst reset to 0, thermalmonitord has control")
} else {
print("[reset] Ftst reset FAILED")
}
#else
print("[reset] not needed on Intel Macs")
#endif
case .help, .unknown:
print("SMC tool\n")
print("Usage:")
print(" ./smc [command]\n")
print("Available Commands:")
print(" list list keys and values")
print(" set set value to a key")
print(" fan set fan speed")
print(" fans list of fans")
print(" reset reset Ftst (Apple Silicon only)")
print(" help help menu\n")
print("Available Flags:")
print(" -t list temperature sensors")
print(" -v list voltage sensors (list cmd) / value (set cmd)")
print(" -p list power sensors")
print(" -f list fans\n")
}
}
main()