Compare commits
14 Commits
7ab5116e5d
...
ef4d8cb44d
| Author | SHA1 | Date | |
|---|---|---|---|
| ef4d8cb44d | |||
| d4daad2071 | |||
| c27f72a8a8 | |||
| 2b4054b44b | |||
| 6372695b16 | |||
| c21a2834b7 | |||
| 2c851c7561 | |||
| 1305b1cda1 | |||
| 67fbea93a4 | |||
| 1646dfcd90 | |||
| 8b35df2989 | |||
| 51353cecd0 | |||
| 17abe4651e | |||
| 412034ea9f |
@@ -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).
|
||||
+1
-1
@@ -25,7 +25,6 @@ ln -sf "$(pwd)/networkmanager-dmenu/config.ini" ~/.config/networkmanager-dmenu/c
|
||||
ln -sf "$(pwd)/niri/config.kdl" ~/.config/niri/config.kdl
|
||||
ln -sf "$(pwd)/waybar/config.jsonc" ~/.config/waybar/config.jsonc
|
||||
ln -sf "$(pwd)/waybar/style.css" ~/.config/waybar/style.css
|
||||
ln -sf "$(pwd)/waybar/mouse-battery.sh" ~/.config/waybar/mouse-battery.sh
|
||||
mkdir -p ~/.config/theme
|
||||
ln -sf "$(pwd)/theme/colors.css" ~/.config/theme/colors.css
|
||||
ln -sf "$(pwd)/wofi/config" ~/.config/wofi/config
|
||||
@@ -59,6 +58,7 @@ ln -sf "$(pwd)/scripts/clipboard.sh" ~/.local/bin/clipboard-picker
|
||||
ln -sf "$(pwd)/scripts/power-profile.sh" ~/.local/bin/power-profile
|
||||
ln -sf "$(pwd)/scripts/fan-profile.sh" ~/.local/bin/fan-profile
|
||||
ln -sf "$(pwd)/scripts/screenshot.sh" ~/.local/bin/screenshot
|
||||
ln -sf "$(pwd)/scripts/waybar-restart.sh" ~/.local/bin/waybar-restart
|
||||
|
||||
echo "==> Enabling systemd user services"
|
||||
mkdir -p ~/.config/systemd/user
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
background-color=#1d1f21
|
||||
text-color=#c5c8c6
|
||||
border-size=2
|
||||
border-color=#81a2be
|
||||
default-timeout=4000
|
||||
+3
-2
@@ -33,9 +33,9 @@ input {
|
||||
|
||||
spawn-at-startup "waybar"
|
||||
spawn-at-startup "mako"
|
||||
spawn-at-startup "swww-daemon"
|
||||
spawn-at-startup "awww-daemon"
|
||||
spawn-at-startup "nm-applet" "--indicator"
|
||||
spawn-at-startup "polkit-gnome-authentication-agent-1"
|
||||
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
|
||||
spawn-at-startup "swayidle" "-w" "timeout" "300" "niri msg action power-off-monitors" "timeout" "600" "gtklock" "-d" "timeout" "1800" "systemctl suspend" "before-sleep" "gtklock" "-d"
|
||||
spawn-at-startup "wl-paste" "--watch" "cliphist" "store"
|
||||
spawn-at-startup "blueman-applet"
|
||||
@@ -52,6 +52,7 @@ binds {
|
||||
Mod+Return { spawn "alacritty"; }
|
||||
Mod+D { spawn "wofi" "--show" "drun"; }
|
||||
Mod+E { spawn "thunar"; }
|
||||
Mod+Shift+B { spawn "waybar-restart"; }
|
||||
|
||||
Mod+H { focus-column-left; }
|
||||
Mod+L { focus-column-right; }
|
||||
|
||||
+2
-1
@@ -3,7 +3,7 @@ waybar
|
||||
wofi
|
||||
mako
|
||||
alacritty
|
||||
swww
|
||||
awww
|
||||
grim
|
||||
slurp
|
||||
swappy
|
||||
@@ -38,3 +38,4 @@ xwayland-satellite
|
||||
power-profiles-daemon
|
||||
fw-fanctrl
|
||||
satty
|
||||
starship
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
waybar &
|
||||
mako &
|
||||
swww-daemon &
|
||||
nm-applet --indicator &
|
||||
polkit-gnome-authentication-agent-1 &
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
pkill -x waybar
|
||||
while pgrep -x waybar >/dev/null; do sleep 0.05; done
|
||||
setsid -f waybar </dev/null >/dev/null 2>&1
|
||||
+25
-23
@@ -5,14 +5,16 @@
|
||||
|
||||
"modules-left": ["niri/workspaces"],
|
||||
"modules-center": ["custom/clock"],
|
||||
"modules-right": ["cpu", "temperature", "custom/power-profile", "custom/fan-profile", "pulseaudio", "network", "battery", "custom/mouse-battery", "tray"],
|
||||
"custom/mouse-battery": {
|
||||
"exec": "~/.config/waybar/mouse-battery.sh",
|
||||
"interval": 60,
|
||||
"format": " {output}",
|
||||
"tooltip": false
|
||||
},
|
||||
|
||||
"modules-right": [
|
||||
"cpu",
|
||||
"temperature",
|
||||
"custom/power-profile",
|
||||
"custom/fan-profile",
|
||||
"pulseaudio",
|
||||
"network",
|
||||
"battery",
|
||||
"tray",
|
||||
],
|
||||
"cpu": {
|
||||
"format": " {usage}%",
|
||||
"format-tooltip": "{usage}% total\n{icon0} {icon1} {icon2} {icon3}\n{icon4} {icon5} {icon6} {icon7}\n{icon8} {icon9} {icon10} {icon11}",
|
||||
@@ -20,8 +22,8 @@
|
||||
"interval": 3,
|
||||
"states": {
|
||||
"warning": 60,
|
||||
"critical": 85
|
||||
}
|
||||
"critical": 85,
|
||||
},
|
||||
},
|
||||
|
||||
"temperature": {
|
||||
@@ -31,8 +33,8 @@
|
||||
"format-critical": " {temperatureC}°C",
|
||||
"states": {
|
||||
"warm": 60,
|
||||
"critical": 80
|
||||
}
|
||||
"critical": 80,
|
||||
},
|
||||
},
|
||||
|
||||
"custom/power-profile": {
|
||||
@@ -40,7 +42,7 @@
|
||||
"return-type": "json",
|
||||
"interval": 30,
|
||||
"signal": 8,
|
||||
"on-click": "~/.local/bin/power-profile --menu"
|
||||
"on-click": "~/.local/bin/power-profile --menu",
|
||||
},
|
||||
|
||||
"custom/fan-profile": {
|
||||
@@ -48,40 +50,40 @@
|
||||
"return-type": "json",
|
||||
"interval": 5,
|
||||
"signal": 9,
|
||||
"on-click": "~/.local/bin/fan-profile --menu"
|
||||
"on-click": "~/.local/bin/fan-profile --menu",
|
||||
},
|
||||
|
||||
"custom/clock": {
|
||||
"exec": "date '+%a %b %d %H:%M'",
|
||||
"exec": "date '+%a %b %d %-I:%M %p'",
|
||||
"interval": 30,
|
||||
"format": "{}",
|
||||
"tooltip": false
|
||||
"tooltip": false,
|
||||
},
|
||||
|
||||
"battery": {
|
||||
"format": "{capacity}% {icon}",
|
||||
"format-charging": "{capacity}% ",
|
||||
"format-plugged": "{capacity}% ",
|
||||
"format-icons": ["","","","",""],
|
||||
"format-icons": ["", "", "", "", ""],
|
||||
"states": {
|
||||
"warning": 30,
|
||||
"critical": 15
|
||||
}
|
||||
"critical": 15,
|
||||
},
|
||||
},
|
||||
|
||||
"tray": {
|
||||
"spacing": 8
|
||||
"spacing": 8,
|
||||
},
|
||||
|
||||
"network": {
|
||||
"format-wifi": " {essid}",
|
||||
"format-ethernet": "",
|
||||
"format-disconnected": "⚠",
|
||||
"on-click": "networkmanager_dmenu"
|
||||
"on-click": "networkmanager_dmenu",
|
||||
},
|
||||
|
||||
"pulseaudio": {
|
||||
"format": " {volume}%",
|
||||
"format-muted": ""
|
||||
}
|
||||
"format-muted": "",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Generic mouse battery script for Waybar
|
||||
|
||||
MOUSE_BATTERY=$(upower -e | grep -i mouse | head -n1)
|
||||
|
||||
if [ -n "$MOUSE_BATTERY" ]; then
|
||||
upower -i "$MOUSE_BATTERY" | awk '/percentage:/ { print $2 }'
|
||||
else
|
||||
echo "N/A"
|
||||
fi
|
||||
Reference in New Issue
Block a user