feat(menu): Back on every submenu + working snapshot fallback
Two menu improvements. Back everywhere: every list menu now ends with a "↩ Back" entry that returns one level up — power, theme, power-profile, clipboard, files, capture, not just Tools/System — so you no longer have to Esc out and reopen to back up. A `back` helper + a shared `BACK` label keep it uniform; matched exactly so it can't collide with clipboard/filename content. Esc still quits instantly. Snapshot fallback: btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil ABI), so `nomarchy-menu snapshot` now launches nomarchy-snapshots instead — a keyboard-driven snapper browser/restore. snapper is root-only here and rofi can't run as root under Wayland, so it runs in a terminal via sudo (one password prompt) and uses fzf to pick: browse/diff (read-only), restore files (undochange), or roll back (root config only), each behind a typed-`yes` confirmation — safer than a one-click rofi rollback. btrfs-assistant stays installed for when nixpkgs fixes it. Verified: flake check clean; home generation builds and the menu passes bash -n with all Back + snapshot wiring; nomarchy-snapshots passes shellcheck (writeShellApplication) in a snapper-enabled system build. Pending an on-machine check of the restore/rollback paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,14 @@ let
|
||||
# rofi has show-icons (on globally; passed explicitly on the icon menus).
|
||||
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
|
||||
|
||||
# General menu convention: every list menu ends with a "↩ Back" entry that
|
||||
# returns one level up (so you never have to Esc out and reopen). `back`
|
||||
# emits it with an icon; plain pick-lists append the bare label instead.
|
||||
# Always matched EXACTLY ("↩ Back") so it can't collide with clipboard or
|
||||
# filename content. The arrow + go-previous icon read as "go back".
|
||||
BACK="↩ Back"
|
||||
back() { row "$BACK" go-previous; }
|
||||
|
||||
case "''${1:-}" in
|
||||
power)
|
||||
choice=$( {
|
||||
@@ -73,8 +81,10 @@ let
|
||||
row "Hibernate" system-hibernate
|
||||
row "Reboot" system-reboot
|
||||
row "Shutdown" system-shutdown
|
||||
back
|
||||
} | rofi -dmenu -show-icons -p Power) || exit 0
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" ;;
|
||||
*Lock) loginctl lock-session ;;
|
||||
*Logout) hyprctl dispatch exit ;;
|
||||
*Suspend) systemctl suspend ;;
|
||||
@@ -92,17 +102,19 @@ let
|
||||
exit 0
|
||||
fi
|
||||
cur=$(powerprofilesctl get 2>/dev/null)
|
||||
choice=$(powerprofilesctl list 2>/dev/null \
|
||||
| sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \
|
||||
choice=$( { powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p'; printf '%s\n' "$BACK"; } \
|
||||
| rofi -dmenu -p "profile (now: $cur)") || exit 0
|
||||
[ "$choice" = "$BACK" ] && exec "$0" system
|
||||
[ -n "$choice" ] && powerprofilesctl set "$choice" ;;
|
||||
|
||||
theme)
|
||||
choice=$(nomarchy-theme-sync list | rofi -dmenu -p theme) || exit 0
|
||||
choice=$( { nomarchy-theme-sync list; printf '%s\n' "$BACK"; } | rofi -dmenu -p theme) || exit 0
|
||||
[ "$choice" = "$BACK" ] && exec "$0"
|
||||
[ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;;
|
||||
|
||||
clipboard)
|
||||
sel=$(cliphist list | rofi -dmenu -p clip) || exit 0
|
||||
sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi -dmenu -p clip) || exit 0
|
||||
[ "$sel" = "$BACK" ] && exec "$0" tools
|
||||
printf '%s' "$sel" | cliphist decode | wl-copy ;;
|
||||
|
||||
calc)
|
||||
@@ -114,10 +126,10 @@ let
|
||||
-calc-command "echo -n '{result}' | wl-copy" ;;
|
||||
|
||||
files)
|
||||
sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
|
||||
| head -n 50000 \
|
||||
| sed "s|^$HOME/||" \
|
||||
sel=$( { fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
|
||||
| head -n 50000 | sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \
|
||||
| rofi -dmenu -p file) || exit 0
|
||||
[ "$sel" = "$BACK" ] && exec "$0" tools
|
||||
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
|
||||
|
||||
emoji)
|
||||
@@ -145,10 +157,12 @@ let
|
||||
row "Region → file" applets-screenshooter
|
||||
row "Full screen → clipboard" camera-photo
|
||||
row "Full screen → file" camera-photo
|
||||
back
|
||||
} | rofi -dmenu -show-icons -p Capture) || exit 0
|
||||
dir="$HOME/Pictures/Screenshots"
|
||||
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" tools ;;
|
||||
*"Region → clipboard"*) grim -g "$(slurp)" - | wl-copy ;;
|
||||
*"Region → file"*) mkdir -p "$dir"; grim -g "$(slurp)" "$file" \
|
||||
&& notify-send "Screenshot saved" "$file" ;;
|
||||
@@ -187,12 +201,14 @@ let
|
||||
notify-send "Night light" "Not available (nomarchy.nightlight off?)."; exit 0 ;;
|
||||
|
||||
snapshot)
|
||||
# btrfs-assistant: snapshot browse / diff / restore / rollback over
|
||||
# snapper, elevating via polkit. Shipped system-side with
|
||||
# nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
|
||||
command -v btrfs-assistant >/dev/null 2>&1 \
|
||||
|| { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
|
||||
exec btrfs-assistant ;;
|
||||
# btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil ABI), so launch
|
||||
# the keyboard-driven snapper browser instead: a terminal running it
|
||||
# via sudo (snapper is root-only here — one password prompt), with
|
||||
# browse/diff, restore (undochange) and rollback, each confirmed.
|
||||
# Self-gated to nomarchy.system.snapper, so it no-ops elsewhere.
|
||||
command -v nomarchy-snapshots >/dev/null 2>&1 \
|
||||
|| { notify-send "Snapshots" "Snapshot tools unavailable (nomarchy.system.snapper off?)."; exit 0; }
|
||||
exec ${cfg.terminal} -e sudo nomarchy-snapshots ;;
|
||||
|
||||
tools)
|
||||
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
||||
@@ -205,9 +221,10 @@ let
|
||||
row "Web search" system-search
|
||||
row "Capture" applets-screenshooter
|
||||
row "Ask Claude" internet-chat
|
||||
row "Back" go-previous
|
||||
back
|
||||
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" ;;
|
||||
*Calc*) exec "$0" calc ;;
|
||||
*Clipboard*) exec "$0" clipboard ;;
|
||||
*Emoji*) exec "$0" emoji ;;
|
||||
@@ -215,7 +232,6 @@ let
|
||||
*Web*) exec "$0" web ;;
|
||||
*Capture*) exec "$0" capture ;;
|
||||
*Ask*) exec "$0" ask ;;
|
||||
*Back*) exec "$0" ;;
|
||||
esac ;;
|
||||
|
||||
system)
|
||||
@@ -229,20 +245,20 @@ let
|
||||
row "Do Not Disturb" notification-disabled
|
||||
systemctl --user cat hyprsunset.service >/dev/null 2>&1 \
|
||||
&& row "Night light" weather-clear-night
|
||||
command -v btrfs-assistant >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||
command -v nomarchy-snapshots >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||
row "Power profile" preferences-system-power
|
||||
fi
|
||||
row "Back" go-previous
|
||||
back
|
||||
} | rofi -dmenu -show-icons -p System) || exit 0
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" ;;
|
||||
*Network*) exec "$0" network ;;
|
||||
*Bluetooth*) exec "$0" bluetooth ;;
|
||||
*"Do Not Disturb"*) exec "$0" dnd ;;
|
||||
*"Night light"*) exec "$0" nightlight ;;
|
||||
*Snapshots*) exec "$0" snapshot ;;
|
||||
*"Power profile"*) exec "$0" power-profile ;;
|
||||
*Back*) exec "$0" ;;
|
||||
esac ;;
|
||||
|
||||
"")
|
||||
|
||||
@@ -325,8 +325,69 @@ in
|
||||
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
||||
'')
|
||||
# The desktop snapshot manager (browse / diff / restore / rollback over
|
||||
# snapper, elevating via polkit) — what `nomarchy-menu snapshot` launches.
|
||||
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant;
|
||||
# snapper, elevating via polkit). Kept for when nixpkgs fixes its crash.
|
||||
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant
|
||||
# Keyboard-driven snapper browser/restore — the fallback `nomarchy-menu
|
||||
# snapshot` actually launches while btrfs-assistant 2.2 segfaults on 26.05
|
||||
# (libbtrfsutil ABI). Runs as root (snapper is root-only here; the menu
|
||||
# opens it in a terminal via sudo, one password prompt), fzf to pick, with
|
||||
# browse/diff (read-only) and typed-`yes` confirmation before any write.
|
||||
++ lib.optional cfg.snapper.enable (pkgs.writeShellApplication {
|
||||
name = "nomarchy-snapshots";
|
||||
runtimeInputs = with pkgs; [ snapper fzf gawk gnugrep less coreutils systemd ];
|
||||
text = ''
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "nomarchy-snapshots must run as root (snapper needs it) — use sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t configs < <(snapper list-configs | awk 'NR>2 {print $1}' | grep .)
|
||||
if [ "''${#configs[@]}" -eq 0 ]; then
|
||||
echo "No snapper configs found." >&2; exit 1
|
||||
elif [ "''${#configs[@]}" -eq 1 ]; then
|
||||
config="''${configs[0]}"
|
||||
else
|
||||
config=$(printf '%s\n' "''${configs[@]}" | fzf --prompt="snapper config> ") || exit 0
|
||||
fi
|
||||
|
||||
while :; do
|
||||
snap=$(snapper -c "$config" list \
|
||||
| fzf --header-lines=2 --prompt="[$config] pick a snapshot (Esc quits)> ") || exit 0
|
||||
num=$(awk '{print $1}' <<<"$snap")
|
||||
case "$num" in ""|*[!0-9]*) continue ;; esac
|
||||
|
||||
action=$(printf '%s\n' \
|
||||
"Browse changes since #$num (read-only)" \
|
||||
"Restore changed files to #$num (undochange)" \
|
||||
"Roll the system back to #$num (reboot)" \
|
||||
"↩ Back to the snapshot list" \
|
||||
| fzf --prompt="snapshot #$num> ") || exit 0
|
||||
|
||||
case "$action" in
|
||||
Browse*)
|
||||
snapper -c "$config" status "$num..0" | less -R || true ;;
|
||||
Restore*)
|
||||
read -rp "Revert files in '$config' to snapshot #$num? Type yes to confirm: " ans || continue
|
||||
if [ "$ans" = yes ]; then
|
||||
snapper -c "$config" undochange "$num..0"
|
||||
echo "Files restored. Press enter."; read -r _ || true
|
||||
fi ;;
|
||||
Roll*)
|
||||
if [ "$config" != root ]; then
|
||||
echo "Rollback applies to the 'root' config only; use Restore for '$config'. Press enter."
|
||||
read -r _ || true
|
||||
else
|
||||
read -rp "Roll the SYSTEM back to #$num and REBOOT now? Type yes to confirm: " ans || continue
|
||||
if [ "$ans" = yes ]; then
|
||||
snapper -c root rollback "$num"
|
||||
echo "Rolled back — rebooting…"; systemctl reboot
|
||||
fi
|
||||
fi ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
done
|
||||
'';
|
||||
});
|
||||
|
||||
# Don't let boot entries fill the ESP over the years.
|
||||
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
|
||||
|
||||
Reference in New Issue
Block a user