fix(fans): surface SMC write failures to user instead of silent fail#3190
fix(fans): surface SMC write failures to user instead of silent fail#3190marxo126 wants to merge 2 commits intoexelban:masterfrom
Conversation
setFanSpeed, setFanMode, and resetFans previously returned Void and only print()ed on failure, leaving the popup UI in an inconsistent state and giving users no indication when SMC writes were rejected (firmware reject, missing key on a chip generation, etc). Return Bool from these SMC entry points, propagate failure through the SMCHelper completion handlers, and surface a NSAlert in the Sensors popup so users get actionable feedback instead of a silently broken slider. Closes exelban#1166
… surface The previous SMCHelper wiring passed completion only through the proxy's reply block. When the XPC connection itself failed (helper not bootstrapped, helper crashed), the reply was never delivered and the completion was silently dropped — defeating the user-facing alert. Per-call helper now sets remoteObjectProxyWithErrorHandler to invoke the caller's completion with a typed error string, so XPC failures take the same surfacing path as helper-side rejects. Verified by booting out the helper service and confirming the alert displays.
|
Update — runtime verified end-to-end on macOS 26.4.1, Apple Silicon (M4 Max). Test setup:
Result: NSAlert pops with the expected "Fan control failed" + propagated error string ("Fan helper unreachable: Couldn't communicate with a helper application"). Confirmed via popup screenshot. One real bug surfaced during testing — the original wiring passed the completion only through the proxy's reply block, so XPC connection failures (helper bootstrapped-off, helper crashed) silently dropped the completion and the alert never fired. Fix is in commit `2bb88f02`: per-call helper now sets `remoteObjectProxyWithErrorHandler` to invoke the caller's completion with a typed error string, so XPC failures take the same surfacing path as helper-side rejects. The fix is what makes this PR actually deliver its promised behaviour. Caveat unchanged: tested on a (necessarily) unsigned dev build with a matching unsigned helper to bypass the SMAuthorizedClients certificate check. Behaviour in a Developer ID-signed production build is identical — the signature check is independent of the failure-surfacing path. |
Problem
SMC/smc.swiftexposessetFanSpeed,setFanMode, andresetFansasVoid. On any failure path — IOKit error, missing key on a chip generation, firmware reject — the functionprint()s and returns silently. The caller inModules/Sensors/popup.swifthas no way to know the SMC write failed, so the slider/mode UI stays in the wrong state and the user gets no feedback.This is the visible symptom in #1166 ("Unable to set fan speed") and a contributing factor to several other fan reports where users report "fan control didn't take effect".
Change
SMC/smc.swift—setFanSpeed,setFanMode,resetFansnow return@discardableResult Bool. Every error path returnsfalse; success returnstrue.resetFanControlalready returnsBooland is unchanged.SMC/Helper/main.swift— the existing(String?) -> Voidcompletion now receives a non-nil error string on failure (wasnilfor both success and failure, which made errors indistinguishable). Error format:"Failed to set fan <id> speed to <rpm> RPM — SMC rejected write".Kit/helpers.swift—SMCHelpersetters gain an optional((String?) -> Void)?completion (defaultnilpreserves all existing call sites). Forwards XPC error string through.Modules/Sensors/popup.swift— addedshowFanError(_:)private helper onFanViewthat dispatches anNSAlert("Fan control failed",.warningstyle) to the main queue. All 8 interactive call sites — init restore, mode buttons (auto/manual/off/turbo),setSpeeddebouncer,wakeListener— pass a completion that callsshowFanErroron a non-nil error.sleepListenerintentionally stays fire-and-forget (the user can't act on a sleep-time error).Public API note
Returning
Boolfrom previously-Voidfunctions is a public API change inside the SMC layer. Marked@discardableResultto avoid noise at existing call sites that don't care about the result. If you'd prefer a non-API-changing approach (separatetryFanSpeedetc), happy to refactor.Net diff
Testing
Logic-reviewed but not yet hardware-tested. Marking Draft. The XPC completion fires on an internal XPC queue per Apple's docs, so the alert dispatches via
DispatchQueue.main.asyncbeforeNSAlert.runModal()— please double-check that pattern matches your house style for the popup layer.