docs: spec for fan-profile auto mode

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-01 09:58:54 -07:00
parent d4daad2071
commit ef4d8cb44d
@@ -0,0 +1,142 @@
# 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 <choice>`, 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 <mapped>` once.
3. Signals waybar to refresh.
Selecting any non-`auto` strategy:
1. Removes the state file (if it exists).
2. Calls `fw-fanctrl use <choice>` (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 <mapped>
│ │ └── matches → no-op
│ │ emit JSON: text="auto", tooltip="auto → <mapped>", 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).