Files
dotfiles/docs/superpowers/plans/2026-05-11-volume-notification.md
T
funman300 cc17e7bab4 docs: implementation plan for volume notification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:09:40 -07:00

7.6 KiB
Raw Blame History

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 (9799) modified to call volume up/down/mute instead of pamixer directly.

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

  • Step 1: Replace the three keybind lines

Find lines 9799 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 and Volume: 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 bogus prints the usage message and exits 1.