Compare commits
14 Commits
| 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)/niri/config.kdl" ~/.config/niri/config.kdl
|
||||||
ln -sf "$(pwd)/waybar/config.jsonc" ~/.config/waybar/config.jsonc
|
ln -sf "$(pwd)/waybar/config.jsonc" ~/.config/waybar/config.jsonc
|
||||||
ln -sf "$(pwd)/waybar/style.css" ~/.config/waybar/style.css
|
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
|
mkdir -p ~/.config/theme
|
||||||
ln -sf "$(pwd)/theme/colors.css" ~/.config/theme/colors.css
|
ln -sf "$(pwd)/theme/colors.css" ~/.config/theme/colors.css
|
||||||
ln -sf "$(pwd)/wofi/config" ~/.config/wofi/config
|
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/power-profile.sh" ~/.local/bin/power-profile
|
||||||
ln -sf "$(pwd)/scripts/fan-profile.sh" ~/.local/bin/fan-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/screenshot.sh" ~/.local/bin/screenshot
|
||||||
|
ln -sf "$(pwd)/scripts/waybar-restart.sh" ~/.local/bin/waybar-restart
|
||||||
|
|
||||||
echo "==> Enabling systemd user services"
|
echo "==> Enabling systemd user services"
|
||||||
mkdir -p ~/.config/systemd/user
|
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 "waybar"
|
||||||
spawn-at-startup "mako"
|
spawn-at-startup "mako"
|
||||||
spawn-at-startup "swww-daemon"
|
spawn-at-startup "awww-daemon"
|
||||||
spawn-at-startup "nm-applet" "--indicator"
|
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 "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 "wl-paste" "--watch" "cliphist" "store"
|
||||||
spawn-at-startup "blueman-applet"
|
spawn-at-startup "blueman-applet"
|
||||||
@@ -52,6 +52,7 @@ binds {
|
|||||||
Mod+Return { spawn "alacritty"; }
|
Mod+Return { spawn "alacritty"; }
|
||||||
Mod+D { spawn "wofi" "--show" "drun"; }
|
Mod+D { spawn "wofi" "--show" "drun"; }
|
||||||
Mod+E { spawn "thunar"; }
|
Mod+E { spawn "thunar"; }
|
||||||
|
Mod+Shift+B { spawn "waybar-restart"; }
|
||||||
|
|
||||||
Mod+H { focus-column-left; }
|
Mod+H { focus-column-left; }
|
||||||
Mod+L { focus-column-right; }
|
Mod+L { focus-column-right; }
|
||||||
|
|||||||
+2
-1
@@ -3,7 +3,7 @@ waybar
|
|||||||
wofi
|
wofi
|
||||||
mako
|
mako
|
||||||
alacritty
|
alacritty
|
||||||
swww
|
awww
|
||||||
grim
|
grim
|
||||||
slurp
|
slurp
|
||||||
swappy
|
swappy
|
||||||
@@ -38,3 +38,4 @@ xwayland-satellite
|
|||||||
power-profiles-daemon
|
power-profiles-daemon
|
||||||
fw-fanctrl
|
fw-fanctrl
|
||||||
satty
|
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
|
||||||
+24
-22
@@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
"modules-left": ["niri/workspaces"],
|
"modules-left": ["niri/workspaces"],
|
||||||
"modules-center": ["custom/clock"],
|
"modules-center": ["custom/clock"],
|
||||||
"modules-right": ["cpu", "temperature", "custom/power-profile", "custom/fan-profile", "pulseaudio", "network", "battery", "custom/mouse-battery", "tray"],
|
"modules-right": [
|
||||||
"custom/mouse-battery": {
|
"cpu",
|
||||||
"exec": "~/.config/waybar/mouse-battery.sh",
|
"temperature",
|
||||||
"interval": 60,
|
"custom/power-profile",
|
||||||
"format": " {output}",
|
"custom/fan-profile",
|
||||||
"tooltip": false
|
"pulseaudio",
|
||||||
},
|
"network",
|
||||||
|
"battery",
|
||||||
|
"tray",
|
||||||
|
],
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"format": " {usage}%",
|
"format": " {usage}%",
|
||||||
"format-tooltip": "{usage}% total\n{icon0} {icon1} {icon2} {icon3}\n{icon4} {icon5} {icon6} {icon7}\n{icon8} {icon9} {icon10} {icon11}",
|
"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,
|
"interval": 3,
|
||||||
"states": {
|
"states": {
|
||||||
"warning": 60,
|
"warning": 60,
|
||||||
"critical": 85
|
"critical": 85,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"temperature": {
|
"temperature": {
|
||||||
@@ -31,8 +33,8 @@
|
|||||||
"format-critical": " {temperatureC}°C",
|
"format-critical": " {temperatureC}°C",
|
||||||
"states": {
|
"states": {
|
||||||
"warm": 60,
|
"warm": 60,
|
||||||
"critical": 80
|
"critical": 80,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"custom/power-profile": {
|
"custom/power-profile": {
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
"interval": 30,
|
"interval": 30,
|
||||||
"signal": 8,
|
"signal": 8,
|
||||||
"on-click": "~/.local/bin/power-profile --menu"
|
"on-click": "~/.local/bin/power-profile --menu",
|
||||||
},
|
},
|
||||||
|
|
||||||
"custom/fan-profile": {
|
"custom/fan-profile": {
|
||||||
@@ -48,14 +50,14 @@
|
|||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
"interval": 5,
|
"interval": 5,
|
||||||
"signal": 9,
|
"signal": 9,
|
||||||
"on-click": "~/.local/bin/fan-profile --menu"
|
"on-click": "~/.local/bin/fan-profile --menu",
|
||||||
},
|
},
|
||||||
|
|
||||||
"custom/clock": {
|
"custom/clock": {
|
||||||
"exec": "date '+%a %b %d %H:%M'",
|
"exec": "date '+%a %b %d %-I:%M %p'",
|
||||||
"interval": 30,
|
"interval": 30,
|
||||||
"format": "{}",
|
"format": "{}",
|
||||||
"tooltip": false
|
"tooltip": false,
|
||||||
},
|
},
|
||||||
|
|
||||||
"battery": {
|
"battery": {
|
||||||
@@ -65,23 +67,23 @@
|
|||||||
"format-icons": ["", "", "", "", ""],
|
"format-icons": ["", "", "", "", ""],
|
||||||
"states": {
|
"states": {
|
||||||
"warning": 30,
|
"warning": 30,
|
||||||
"critical": 15
|
"critical": 15,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"tray": {
|
"tray": {
|
||||||
"spacing": 8
|
"spacing": 8,
|
||||||
},
|
},
|
||||||
|
|
||||||
"network": {
|
"network": {
|
||||||
"format-wifi": " {essid}",
|
"format-wifi": " {essid}",
|
||||||
"format-ethernet": "",
|
"format-ethernet": "",
|
||||||
"format-disconnected": "⚠",
|
"format-disconnected": "⚠",
|
||||||
"on-click": "networkmanager_dmenu"
|
"on-click": "networkmanager_dmenu",
|
||||||
},
|
},
|
||||||
|
|
||||||
"pulseaudio": {
|
"pulseaudio": {
|
||||||
"format": " {volume}%",
|
"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