# Fan-Profile "Auto" Mode Design **Date:** 2026-05-01 **Status:** Approved ## Summary Add a virtual `auto` strategy to the waybar fan-profile module. When auto is on, `scripts/fan-profile.sh` keeps `fw-fanctrl` in sync with the active `power-profiles-daemon` profile — `power-saver` → `lazy`, `balanced` → `medium`, `performance` → `agile`. Reconciliation rides waybar's existing 5 s poll; no new daemon, no udev hooks, no fw-fanctrl strategy edits. ## Current Behaviour `scripts/fan-profile.sh` is invoked by waybar's `custom/fan-profile` module on a 5 s interval and on click: - **Status emit:** runs `fw-fanctrl print`, parses out the active strategy and current speed, prints a JSON line with an icon and tooltip. - **`--menu`:** runs `fw-fanctrl print list`, pipes the strategy names through wofi, calls `fw-fanctrl use `, signals `pkill -RTMIN+9 waybar` so the bar refreshes. The strategy list is whatever `fw-fanctrl print list` returns: `laziest, lazy, medium, agile, very-agile, deaf, aeolus`. There is currently no notion of an automatic mode. ## Target Behaviour ### Wofi menu A new entry `auto` is prepended to the strategy list before passing it to wofi. Selecting it: 1. Touches the state file `~/.local/state/fan-profile-auto`. 2. Resolves the current power profile and calls `fw-fanctrl use ` once. 3. Signals waybar to refresh. Selecting any non-`auto` strategy: 1. Removes the state file (if it exists). 2. Calls `fw-fanctrl use ` (current behaviour, unchanged). 3. Signals waybar to refresh. ### Status emit (every 5 s) ``` state file exists? ├── yes → read powerprofilesctl get │ ├── ok → resolve mapped strategy │ │ ├── differs from fw-fanctrl's active → fw-fanctrl use │ │ └── matches → no-op │ │ emit JSON: text="auto", tooltip="auto → ", class="auto" │ └── error → emit JSON: text="auto (?)", tooltip="auto: power-profiles-daemon unreachable", class="auto" └── no → existing behaviour: emit fw-fanctrl's active strategy ``` ### Mapping | power-profiles-daemon | fw-fanctrl | |---|---| | `power-saver` | `lazy` | | `balanced` | `medium` | | `performance` | `agile` | Hardcoded in the script — small, stable, doesn't need a config file. If a profile name comes back unrecognized (e.g. a custom profile), the script falls into the error branch above (`auto (?)`). ## Implementation ### `scripts/fan-profile.sh` (only file changed) Three additions to the existing script: 1. **State path constant** at top: ```bash STATE_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/fan-profile-auto" ``` 2. **Mapping function**: ```bash map_profile_to_strategy() { case "$1" in power-saver) echo lazy ;; balanced) echo medium ;; performance) echo agile ;; *) return 1 ;; esac } ``` 3. **Menu branch** — prepend `auto` and route the choice: ```bash if [ "$1" = "--menu" ]; then STRATS=$(fw-fanctrl print list 2>/dev/null | grep "^-" | sed 's/^- //') CHOICE=$(printf 'auto\n%s\n' "$STRATS" | wofi --dmenu --prompt "Fan Strategy:" \ --width 260 --height 320 --hide-scroll --no-actions --insensitive) [ -z "$CHOICE" ] && exit if [ "$CHOICE" = "auto" ]; then mkdir -p "$(dirname "$STATE_FILE")" touch "$STATE_FILE" PROFILE=$(powerprofilesctl get 2>/dev/null) && \ MAPPED=$(map_profile_to_strategy "$PROFILE") && \ fw-fanctrl use "$MAPPED" else rm -f "$STATE_FILE" fw-fanctrl use "$CHOICE" fi pkill -RTMIN+9 waybar exit fi ``` 4. **Status branch** — wrap the existing emit with auto-mode handling: ```bash if [ -f "$STATE_FILE" ]; then PROFILE=$(powerprofilesctl get 2>/dev/null) MAPPED=$(map_profile_to_strategy "$PROFILE" 2>/dev/null) if [ -n "$MAPPED" ]; then ACTIVE=$(fw-fanctrl print 2>/dev/null | awk -F"'" '/^Strategy:/{print $2}') [ "$ACTIVE" != "$MAPPED" ] && fw-fanctrl use "$MAPPED" >/dev/null 2>&1 printf '{"text": "󰈐 auto", "tooltip": "Auto fan strategy\\nProfile: %s → %s\\nClick to change", "class": "auto"}\n' "$PROFILE" "$MAPPED" else printf '{"text": "󰈐 auto (?)", "tooltip": "Auto: power-profiles-daemon unreachable or unknown profile\\nClick to change", "class": "auto"}\n' fi exit fi # ... existing status emit (fw-fanctrl print → icon/level → JSON) ... ``` ### Reconciliation cadence Waybar polls `custom/fan-profile` every 5 s (`"interval": 5` in `waybar/config.jsonc`). Each poll runs the script, which reads the state file and reconciles. No additional daemon, signal, or hook is needed. Worst-case desync: 5 s after a power-profile change. The existing `pkill -RTMIN+9 waybar` from `power-profile.sh --menu` will trigger an immediate refresh when the user changes profile via the bar — so in the common case, the fan strategy switches within waybar's render cycle, not 5 s later. ### Persistence State file lives at `$XDG_STATE_HOME/fan-profile-auto` (default `~/.local/state/fan-profile-auto`). XDG state dir survives reboot, so auto stays on across sessions. Removing the file (or selecting any non-auto strategy) disables auto mode. ## Files Touched - `scripts/fan-profile.sh` — three additions (state constant, mapping function, two new branches in menu and status handlers). No CSS, no waybar config, no install.sh changes. ## Out of Scope - Syncing the auto-on state across machines (would require moving the state file into `~/.config/` and tracking it in the dotfiles repo). - Mapping to strategies other than `lazy`/`medium`/`agile`. The user can edit the mapping function inline if they want different curves later. - Reacting to anything other than power-profiles-daemon (CPU temp, AC vs battery, etc.). Those were rejected during brainstorming in favour of the cleaner power-profile coupling. - A separate icon for auto mode. Reuses the existing `󰈐` glyph; the `auto` text in the bar is the differentiator. - Tracking `/etc/fw-fanctrl/config.json` in the dotfiles repo (raised during clarifications, not adopted — auto doesn't need any new fw-fanctrl strategies).