This adds a LaunchDaemon that applies saved fan minimum RPM settings at boot, before the user logs in. This is critical for OCLP (OpenCore Legacy Patcher) Macs where the SMC fan controller defaults to maximum speed until software sets the minimum RPM.
Boot → launchd loads com.smcfancontrol.bootdaemon.plist
→ runs /Library/Application Support/smcFanControl/smcfancontrold
→ detects OCLP (skips if not OCLP, unless forced)
→ reads /Library/Application Support/smcFanControl/fan-settings.plist
→ writes F{n}Mn keys to SMC
→ logs to /var/log/smcfancontrold.log
→ exits
FanControlHelper/smcfancontrold.c— The daemon binary sourceFanControlHelper/OCLPDetect.h— OCLP detection API (C header)FanControlHelper/OCLPDetect.c— OCLP detection implementationFanControlHelper/com.smcfancontrol.bootdaemon.plist— LaunchDaemon plistFanControlHelper/fan-settings-example.plist— Example configFanControlHelper/Makefile— Standalone build/install
Classes/OCLPHelper.h— Objective-C OCLP helper APIClasses/OCLPHelper.m— OCLP detection, daemon install/uninstall, settings sync
Classes/FanControl.m— Added OCLPHelper import, first-launch prompt, settings sync
- In Xcode, File → New → Target → macOS → Command Line Tool
- Name:
smcfancontrold, Language: C - Add these source files to the target:
FanControlHelper/smcfancontrold.cFanControlHelper/OCLPDetect.cFanControlHelper/OCLPDetect.h
- Link frameworks:
IOKit.framework,CoreFoundation.framework - Build Settings:
MACOSX_DEPLOYMENT_TARGET=10.13GCC_PREPROCESSOR_DEFINITIONS= (none needed, this is standalone)
- Under the main app target → Build Phases → Copy Bundle Resources:
- Add the built
smcfancontroldbinary - Add
com.smcfancontrol.bootdaemon.plist
- Add the built
- Add a "Target Dependency" from the main app to
smcfancontrold
- Add
Classes/OCLPHelper.handClasses/OCLPHelper.mto the smcFanControl target - The
#import "OCLPHelper.h"is already added toFanControl.m
# Build daemon standalone
cd FanControlHelper
make
# Test OCLP detection (dry run)
sudo ./smcfancontrold -n
# Test with force flag (non-OCLP Mac)
sudo ./smcfancontrold -f -c fan-settings-example.plist
# Full install
sudo make install- User adjusts fan slider in smcFanControl.app
fanSliderChanged:writes to SMC and saves to NSUserDefaults[OCLPHelper syncFanSettingsWithDaemon]writes to system-level plist- On next boot,
smcfancontroldreads that plist and applies via SMC
The daemon checks four indicators (any one is sufficient):
| Method | Check | Reliability |
|---|---|---|
| Directory | /Library/OpenCore exists |
High — OCLP always creates this |
| NVRAM | opencore-version key present |
High — set by OpenCore bootloader |
| Directory | /Library/Application Support/Dortania |
Medium — OCLP support files |
| Boot args | amfi_get_out_of_my_way, revpatch=, etc. |
Medium — common OCLP flags |
Usage: smcfancontrold [options]
-c <path> Config plist path (default: /Library/Application Support/smcFanControl/fan-settings.plist)
-f Force apply even if OCLP is not detected
-n Dry run (detect OCLP and read config, but don't write SMC)
-q Quiet mode (no log file, stderr only)
-h Show help
- The daemon runs as root (required for SMC writes)
- The LaunchDaemon plist must be owned by root:wheel with 644 permissions
- The daemon binary must be owned by root:wheel with 755 permissions
- Installation requires admin authentication (shown via standard macOS dialog)
- The fan-settings.plist is readable by all but only writable with admin privileges