Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.6 KiB
Volume Notification Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the three pamixer-direct media-key bindings with calls to a wrapper script that also fires a mako notification showing the new volume level (or "Muted") with a progress bar.
Architecture: New scripts/volume.sh wrapper accepting up|down|mute. Adjusts master sink via pamixer, then calls notify-send with mako's x-canonical-private-synchronous and int:value hints so successive notifications replace each other and render as a progress bar. niri keybinds call the wrapper through its ~/.local/bin/volume symlink. install.sh symlinks the script alongside the existing entries.
Tech Stack: bash, pamixer, notify-send, mako (notification daemon), niri.
Spec: docs/superpowers/specs/2026-05-11-volume-notification-design.md
Verification model: Bash syntax check + dry-run script invocation with each subcommand. niri config validity via niri validate. End-to-end (actually pressing the volume key and seeing a mako bubble) requires the live desktop — the controller verifies after both task commits.
File Structure
| File | Role |
|---|---|
scripts/volume.sh |
New wrapper: pamixer adjust + notify-send. Three subcommands: up, down, mute. |
install.sh |
One new ln -sf line in the ==> Installing scripts block. |
niri/config.kdl |
Three lines (97–99) modified to call volume up/down/mute instead of pamixer directly. |
Task 1: Wrapper script + install.sh symlink
After this task, ~/.local/bin/volume up|down|mute works from any shell and produces both the volume change and the mako notification. niri keybinds still call pamixer directly — that swap happens in Task 2.
Files:
-
Create:
scripts/volume.sh -
Modify:
install.sh(append one line in the scripts symlink block) -
Step 1: Create
scripts/volume.sh
Write scripts/volume.sh with this exact content:
#!/bin/bash
case "${1:-}" in
up) pamixer -i 5 ;;
down) pamixer -d 5 ;;
mute) pamixer -t ;;
*) echo "usage: $0 up|down|mute" >&2; exit 1 ;;
esac
if [ "$(pamixer --get-mute)" = "true" ]; then
notify-send -h string:x-canonical-private-synchronous:volume \
-h int:value:0 \
-t 1500 \
"Muted"
else
LEVEL=$(pamixer --get-volume)
notify-send -h string:x-canonical-private-synchronous:volume \
-h int:value:"$LEVEL" \
-t 1500 \
"Volume: ${LEVEL}%"
fi
- Step 2: Make it executable
chmod +x scripts/volume.sh
ls -la scripts/volume.sh
Expected: -rwxr-xr-x mode.
- Step 3: Syntax-check
bash -n scripts/volume.sh && echo "syntax ok"
Expected: syntax ok, exit 0.
- Step 4: Dry-run the usage-error branch
The unrecognised-arg branch is safe to exercise without side effects (pamixer never called):
bash scripts/volume.sh 2>&1
echo "exit=$?"
Expected:
usage: scripts/volume.sh up|down|mute
exit=1
- Step 5: Symlink it into
~/.local/bin
ln -sf "$PWD/scripts/volume.sh" ~/.local/bin/volume
ls -la ~/.local/bin/volume
Expected: symlink pointing to $PWD/scripts/volume.sh (absolute path of repo's script).
- Step 6: Add the symlink line to
install.sh
Find the ==> Installing scripts block in install.sh. It currently ends like this:
ln -sf "$(pwd)/scripts/waybar-restart.sh" ~/.local/bin/waybar-restart
ln -sf "$(pwd)/scripts/screenshot.sh" ~/.local/bin/screenshot
Append one line after screenshot.sh:
ln -sf "$(pwd)/scripts/volume.sh" ~/.local/bin/volume
The block then ends with three ln -sf lines (waybar-restart, screenshot, volume).
- Step 7: Sanity-check install.sh parses
bash -n install.sh && echo "syntax ok"
Expected: syntax ok, exit 0.
- Step 8: Functional smoke test (controller verifies; subagent skip if no audio session)
If you have an audio session, run:
~/.local/bin/volume up
Expected: volume rises 5%, a mako bubble appears with Volume: NN% and a progress bar, the bubble auto-dismisses in 1.5 s. Then run ~/.local/bin/volume down and ~/.local/bin/volume mute and confirm each behaves.
If your sandbox has no audio session or no notification daemon, skip; the controller will verify after the commit.
- Step 9: Commit
git add scripts/volume.sh install.sh
git commit -m "volume: add notification wrapper script"
Task 2: Point niri keybinds at the wrapper
After this task, pressing the three XF86Audio keys runs the wrapper instead of pamixer directly. Volume changes now produce notifications.
Files:
-
Modify:
niri/config.kdl(lines 97–99) -
Step 1: Replace the three keybind lines
Find lines 97–99 in niri/config.kdl:
XF86AudioRaiseVolume allow-when-locked=true { spawn "pamixer" "-i" "5"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "pamixer" "-d" "5"; }
XF86AudioMute allow-when-locked=true { spawn "pamixer" "-t"; }
Replace with:
XF86AudioRaiseVolume allow-when-locked=true { spawn "volume" "up"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "volume" "down"; }
XF86AudioMute allow-when-locked=true { spawn "volume" "mute"; }
allow-when-locked=true stays — same behaviour, just routed through the wrapper.
- Step 2: Validate niri config
niri validate 2>&1 | tail -2
Expected: a final line INFO niri: config is valid. Non-zero exit or ERROR means a syntax issue.
- Step 3: Confirm pamixer is no longer referenced in keybinds
grep -n "pamixer" niri/config.kdl
Expected: zero matches (the wrapper is invoked via volume, pamixer is implementation detail).
- Step 4: Confirm the three new lines are in place
grep -n 'spawn "volume"' niri/config.kdl
Expected: three lines, one each for "up", "down", "mute":
97: XF86AudioRaiseVolume allow-when-locked=true { spawn "volume" "up"; }
98: XF86AudioLowerVolume allow-when-locked=true { spawn "volume" "down"; }
99: XF86AudioMute allow-when-locked=true { spawn "volume" "mute"; }
- Step 5: Functional smoke test (controller verifies)
niri picks up config.kdl changes live; no reload needed. Press the volume up key — a mako notification should appear within ~100 ms showing the new level + progress bar. Press mute — notification shows Muted with empty bar. Press volume up again — notification returns to Volume: NN%.
Each key press should replace the previous notification (no stacking), thanks to the x-canonical-private-synchronous hint.
- Step 6: Commit
git add niri/config.kdl
git commit -m "niri: route volume keys through notification wrapper"
Final verification (controller, post-merge)
- Pressing XF86AudioRaiseVolume / XF86AudioLowerVolume produces a mako notification with the new percentage and a progress bar.
- Pressing XF86AudioMute toggles the mute state; the notification shows
Muted(with empty bar) when muting andVolume: NN%when unmuting. - Holding the key (rapid repeat) produces a single updating bubble, not a stack.
- Volume changes still work on the lock screen (gtklock). The bubble may not be visible while locked depending on mako's layer ordering relative to gtklock; the volume change itself fires regardless.
~/.local/bin/volume bogusprints the usage message and exits 1.