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>
This commit is contained in:
Alex Goodkind
2026-02-22 06:17:23 -08:00
committed by GitHub
parent 28395c6dfd
commit 20030a2a1c
9 changed files with 258 additions and 31 deletions

View File

@@ -71,8 +71,24 @@ internal class Popup: PopupWrapper {
selected: self.fanValueState.rawValue
))
]))
#if arch(arm64)
NotificationCenter.default.addObserver(self, selector: #selector(self.checkFanModesAndResetFtst), name: .checkFanModes, object: nil)
#endif
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#if arch(arm64)
@objc private func checkFanModesAndResetFtst() {
let fanViews = self.list.values.compactMap { $0 as? FanView }
guard !fanViews.isEmpty else { return }
guard fanViews.allSatisfy({ $0.fan.mode == .automatic }) else { return }
SMCHelper.shared.resetFanControl()
}
#endif
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@@ -455,7 +471,7 @@ internal class ChartSensorView: NSStackView {
internal class FanView: NSStackView {
public var sizeCallback: (() -> Void)
private var fan: Fan
internal var fan: Fan
private var ready: Bool = false
private var helperView: NSView? = nil
@@ -638,7 +654,7 @@ internal class FanView: NSStackView {
height: view.frame.height - 8
), mode: self.fan.mode)
buttons.callback = { [weak self] (mode: FanMode) in
if let fan = self?.fan, fan.mode != mode {
if let fan = self?.fan, mode == .automatic || fan.mode != mode {
self?.fan.mode = mode
self?.fan.customMode = mode
SMCHelper.shared.setFanMode(fan.id, mode: mode.rawValue)
@@ -1072,6 +1088,7 @@ private class ModeButtons: NSStackView {
self.callback(.automatic)
NotificationCenter.default.post(name: .syncFansControl, object: nil, userInfo: ["mode": "automatic"])
NotificationCenter.default.post(name: .checkFanModes, object: nil)
}
@objc private func manualMode(_ sender: NSButton) {

View File

@@ -352,6 +352,14 @@ extension SensorsReader {
}
private func getFanMode(_ id: Int) -> FanMode {
#if arch(arm64)
// Apple Silicon: Read F%dMd directly
// Mode values: 0 = auto, 1 = manual, 3 = system (treated as auto for UI)
let modeValue = Int(SMC.shared.getValue("F\(id)Md") ?? 0)
return modeValue == 1 ? .forced : .automatic
#else
// Legacy Intel: Use FS! bitmask
// Bitmask: 0 = all auto, 1 = fan 0 forced, 2 = fan 1 forced, 3 = both forced
let fansMode: Int = Int(SMC.shared.getValue("FS! ") ?? 0)
var mode: FanMode = .automatic
@@ -366,6 +374,7 @@ extension SensorsReader {
}
return mode
#endif
}
}