Files
dotfiles/docs/superpowers/specs/2026-05-01-fan-profile-auto-design.md
T
funman300 ef4d8cb44d docs: spec for fan-profile auto mode
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:58:54 -07:00

6.2 KiB

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-saverlazy, balancedmedium, performanceagile. 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:

    STATE_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/fan-profile-auto"
    
  2. Mapping function:

    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:

    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:

    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).