dotfiles/.config/scripts/control-center.sh
Sarthak 2e6d7a2116 control centre: escape steps back to the main panel
Submenus returned into a case that then fell out of the script, so escape
closed everything. The panel now loops, and only escape on the main list (or
an action that hands the screen to something else) closes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cn3dHfbgNYdbZvzb1b3GQt
2026-09-04 00:58:04 +05:30

315 lines
13 KiB
Bash
Executable file

#!/usr/bin/env bash
# Sakura Line - control centre
#
# One centred panel for network, bluetooth, audio, vpn, session and desktop
# toggles. Submenus reuse the same window, so it reads as a single panel
# rather than a pile of separate menus.
set -uo pipefail
RASI="$HOME/.config/rofi/control.rasi"
# 1 = close the panel entirely, 0 = fall back to the main list
CC_EXIT=0
menu() { # menu <prompt> [rofi args...]
local prompt="$1"; shift
rofi -dmenu -i -theme "$RASI" -p "$prompt" "$@"
}
notify() { notify-send -a "Control" "$@" >/dev/null 2>&1; }
have() { command -v "$1" >/dev/null 2>&1; }
# sxhkd inherits a bare PATH, so ~/.local/bin needs adding back by hand
export PATH="$HOME/.local/bin:$PATH"
# ------------------------------------------------------------------ status
wifi_status() {
have nmcli || { echo "unavailable"; return; }
if [ "$(nmcli -t -f WIFI radio 2>/dev/null)" != "enabled" ]; then echo "off"; return; fi
local eth ssid
eth=$(nmcli -t -f TYPE,STATE device status 2>/dev/null | grep -c '^ethernet:connected')
[ "$eth" -gt 0 ] && { echo "ethernet"; return; }
ssid=$(nmcli -t -f NAME,TYPE connection show --active 2>/dev/null | awk -F: '$2 ~ /wireless/ {print $1; exit}')
[ -n "$ssid" ] && echo "$ssid" || echo "on, not connected"
}
bt_status() {
have bluetoothctl || { echo "unavailable"; return; }
bluetoothctl show 2>/dev/null | grep -q "Powered: yes" || { echo "off"; return; }
local n
n=$(bluetoothctl devices Connected 2>/dev/null | grep -c '^Device')
[ "${n:-0}" -gt 0 ] && echo "$n connected" || echo "on"
}
vol_status() {
have wpctl || { echo "unavailable"; return; }
local out
out=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null) || { echo "unavailable"; return; }
grep -q MUTED <<<"$out" && { echo "muted"; return; }
awk '{printf "%d%%", $2*100}' <<<"$out"
}
mic_status() {
have wpctl || { echo "unavailable"; return; }
local out
out=$(wpctl get-volume @DEFAULT_AUDIO_SOURCE@ 2>/dev/null) || { echo "unavailable"; return; }
grep -q MUTED <<<"$out" && echo "muted" || awk '{printf "%d%%", $2*100}' <<<"$out"
}
vpn_status() {
local ifs
ifs=$(ip -br link show type wireguard 2>/dev/null | awk '{print $1}' | paste -sd, -)
[ -n "$ifs" ] && echo "$ifs" || echo "off"
}
dnd_status() {
have dunstctl || { echo "unavailable"; return; }
[ "$(dunstctl is-paused 2>/dev/null)" = true ] && echo "paused" || echo "on"
}
display_status() {
have xrandr || { echo "unavailable"; return; }
xrandr --current 2>/dev/null | awk '/\*/{print $1; exit}'
}
bar_status() { pgrep -x polybar >/dev/null 2>&1 && echo "shown" || echo "hidden"; }
particles_status() {
have particles || { echo "not installed"; return; }
particles --status 2>/dev/null | grep -q running && echo "on" || echo "off"
}
# ------------------------------------------------------------- submenus
wifi_menu() {
local radio choice ssid pass
radio=$(nmcli -t -f WIFI radio 2>/dev/null)
{
[ "$radio" = enabled ] && echo "󰖪 Turn Wi-Fi off" || echo "󰖩 Turn Wi-Fi on"
echo "󰑓 Rescan"
if [ "$radio" = enabled ]; then
nmcli -t -f IN-USE,SIGNAL,SECURITY,SSID device wifi list 2>/dev/null |
awk -F: 'length($4){
icon = ($1=="*") ? "󰸞" : (($3=="" || $3=="--") ? "󰖩" : "󰤪")
printf "%s %s\t%s%%\n", icon, $4, $2 }' | sort -u -k1,1
fi
} > /tmp/.cc-wifi.$$
choice=$(menu "Wi-Fi" < /tmp/.cc-wifi.$$); rm -f /tmp/.cc-wifi.$$
[ -z "$choice" ] && return
case "$choice" in
*"Turn Wi-Fi off") nmcli radio wifi off; notify "Wi-Fi" "Turned off" ;;
*"Turn Wi-Fi on") nmcli radio wifi on; notify "Wi-Fi" "Turned on" ;;
*Rescan) nmcli device wifi rescan >/dev/null 2>&1; notify "Wi-Fi" "Rescanning"; wifi_menu ;;
*) ssid=$(sed 's/^[^ ]* //; s/\t.*$//' <<<"$choice")
if nmcli -t -f NAME connection show 2>/dev/null | grep -qx "$ssid"; then
nmcli connection up id "$ssid" >/dev/null 2>&1 \
&& notify "Wi-Fi" "Connected to $ssid" || notify -u critical "Wi-Fi" "Failed: $ssid"
else
pass=$(menu "Password for $ssid" -password)
[ -z "$pass" ] && return
nmcli device wifi connect "$ssid" password "$pass" >/dev/null 2>&1 \
&& notify "Wi-Fi" "Connected to $ssid" || notify -u critical "Wi-Fi" "Failed: $ssid"
fi ;;
esac
}
bt_menu() {
local powered choice mac
powered=$(bluetoothctl show 2>/dev/null | grep -q "Powered: yes" && echo yes || echo no)
{
[ "$powered" = yes ] && echo "󰂲 Turn Bluetooth off" || echo "󰂯 Turn Bluetooth on"
if [ "$powered" = yes ]; then
echo "󰑓 Scan for devices"
bluetoothctl devices 2>/dev/null | while read -r _ mac name; do
if bluetoothctl info "$mac" 2>/dev/null | grep -q "Connected: yes"; then
printf "󰂱 %s\t%s\n" "$name" "$mac"
else
printf "󰂲 %s\t%s\n" "$name" "$mac"
fi
done
fi
} > /tmp/.cc-bt.$$
choice=$(menu "Bluetooth" < /tmp/.cc-bt.$$); rm -f /tmp/.cc-bt.$$
[ -z "$choice" ] && return
case "$choice" in
*"Turn Bluetooth off") bluetoothctl power off >/dev/null; notify "Bluetooth" "Turned off" ;;
*"Turn Bluetooth on") bluetoothctl power on >/dev/null; notify "Bluetooth" "Turned on" ;;
*"Scan for devices") notify "Bluetooth" "Scanning for 10s"
bluetoothctl --timeout 10 scan on >/dev/null 2>&1; bt_menu ;;
*) mac=$(awk -F'\t' '{print $2}' <<<"$choice")
[ -z "$mac" ] && return
if bluetoothctl info "$mac" 2>/dev/null | grep -q "Connected: yes"; then
bluetoothctl disconnect "$mac" >/dev/null && notify "Bluetooth" "Disconnected"
else
notify "Bluetooth" "Connecting..."
bluetoothctl connect "$mac" >/dev/null 2>&1 \
&& notify "Bluetooth" "Connected" || notify -u critical "Bluetooth" "Connection failed"
fi ;;
esac
}
audio_menu() {
local choice sink
{
echo "󰝟 Toggle mute $(vol_status)"
echo "󰝞 Volume down -5%"
echo "󰝝 Volume up +5%"
echo "󰍬 Toggle mic mute $(mic_status)"
pactl list short sinks 2>/dev/null | while read -r id name _; do
printf "󰓃 %s\t%s\n" "$(pactl list sinks 2>/dev/null | grep -A2 "Name: $name" | awk -F': ' '/Description/{print $2; exit}')" "$name"
done
} > /tmp/.cc-audio.$$
choice=$(menu "Audio" < /tmp/.cc-audio.$$); rm -f /tmp/.cc-audio.$$
[ -z "$choice" ] && return
case "$choice" in
*"Toggle mute") wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle; audio_menu ;;
*"Volume down"*) wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-; audio_menu ;;
*"Volume up"*) wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ -l 1.0; audio_menu ;;
*"Toggle mic mute") wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle; audio_menu ;;
*) sink=$(awk -F'\t' '{print $2}' <<<"$choice")
[ -n "$sink" ] && pactl set-default-sink "$sink" && notify "Audio" "Output switched" ;;
esac
}
vpn_menu() {
local choice iface up
{
for iface in $(ip -br link show type wireguard 2>/dev/null | awk '{print $1}'); do
printf "󰕥 %s\tdown\n" "$iface"
done
# wg-quick@<name> units systemd knows about, minus the ones already up
systemctl list-units --all --type=service --no-legend 'wg-quick@*' 2>/dev/null |
awk '{print $1}' | sed 's/^wg-quick@//; s/\.service$//' | while read -r n; do
[ -z "$n" ] && continue
ip link show "$n" >/dev/null 2>&1 || printf "󰖂 %s\tup\n" "$n"
done
} > /tmp/.cc-vpn.$$
if [ ! -s /tmp/.cc-vpn.$$ ]; then
echo "󰅚 No WireGuard interfaces found" > /tmp/.cc-vpn.$$
echo "󰐕 Start one by name…" >> /tmp/.cc-vpn.$$
fi
choice=$(menu "VPN" < /tmp/.cc-vpn.$$); rm -f /tmp/.cc-vpn.$$
[ -z "$choice" ] && return
case "$choice" in
*"No WireGuard"*) return ;;
*"Start one by name"*)
iface=$(menu "Interface name (e.g. wg0)")
[ -z "$iface" ] && return
systemctl start "wg-quick@${iface}" && notify "VPN" "$iface up" \
|| notify -u critical "VPN" "Could not start $iface" ;;
*) iface=$(sed 's/^[^ ]* //; s/\t.*$//' <<<"$choice")
up=$(awk -F'\t' '{print $2}' <<<"$choice")
if [ "$up" = down ]; then
systemctl stop "wg-quick@${iface}" && notify "VPN" "$iface down" \
|| notify -u critical "VPN" "Could not stop $iface"
else
systemctl start "wg-quick@${iface}" && notify "VPN" "$iface up" \
|| notify -u critical "VPN" "Could not start $iface"
fi ;;
esac
}
lock_screen() {
if have betterlockscreen; then betterlockscreen -l blur
elif have i3lock; then i3lock -c 0f0d17
elif have xsecurelock; then xsecurelock &
elif have slock; then slock
else notify -u critical "Lock" "No screen locker installed (try i3lock)"; return 1
fi
}
power_menu() {
local choice
choice=$(printf '%s\n' \
"󰌾 Lock" \
"󰍃 Log out" \
"󰒲 Suspend" \
"󰜉 Reboot" \
"󰐥 Shut down" | menu "Power")
[ -z "$choice" ] && return # escape here goes back to the panel
CC_EXIT=1
case "$choice" in
*Lock) lock_screen ;;
*"Log out") bspc quit ;;
*Suspend) lock_screen; systemctl suspend ;;
*Reboot) systemctl reboot ;;
*"Shut down") systemctl poweroff ;;
esac
}
notif_menu() {
local choice paused
paused=$(dunstctl is-paused 2>/dev/null)
choice=$(printf '%s\n' \
"$([ "$paused" = true ] && echo "󰂚 Resume notifications" || echo "󰂛 Do not disturb")" \
"󰋚 Show last notification" \
"󰎟 Show all history" \
"󰆴 Clear all" | menu "Notifications")
case "$choice" in
*"Do not disturb") dunstctl set-paused true; notify "Notifications" "Paused" ;;
*"Resume notifications") dunstctl set-paused false; notify "Notifications" "Resumed" ;;
*"Show last notification") dunstctl history-pop ;;
*"Show all history") for _ in $(seq 6); do dunstctl history-pop; done ;;
*"Clear all") dunstctl close-all ;;
esac
}
display_menu() {
local out choice mode
out=$(xrandr --current 2>/dev/null | awk '/ connected/{print $1; exit}')
[ -z "$out" ] && { notify -u critical "Display" "No output found"; return; }
choice=$(xrandr --current 2>/dev/null | sed -n "/^$out connected/,/^[^ ]/p" |
awk '/^ /{printf "󰍹 %s\t%s\n", $1, $2}' | head -12 | menu "Display ($out)")
[ -z "$choice" ] && return
mode=$(sed 's/^[^ ]* //; s/\t.*$//' <<<"$choice")
xrandr --output "$out" --mode "$mode" && notify "Display" "$out set to $mode"
}
shot_menu() {
local choice
have flameshot || { notify -u critical "Screenshot" "flameshot is not installed"; return; }
choice=$(printf '%s\n' \
"󰩭 Region" \
"󰹑 Full screen" \
"󰆊 Region, delayed 3s" | menu "Screenshot")
[ -z "$choice" ] && return
CC_EXIT=1
case "$choice" in
*Region) flameshot gui ;;
*"Full screen") flameshot full -c -p "$HOME/pictures" 2>/dev/null || flameshot full -c ;;
*"delayed 3s") flameshot gui -d 3000 ;;
esac
}
# ---------------------------------------------------------------- main panel
main_menu() {
local choice
choice=$(printf '%s\n' \
"󰖩 Network $(wifi_status)" \
"󰂯 Bluetooth $(bt_status)" \
"󰕾 Audio $(vol_status)" \
"󰍬 Microphone $(mic_status)" \
"󰖂 VPN $(vpn_status)" \
"󰍺 Display $(display_status)" \
"󰂚 Notifications $(dnd_status)" \
"󰅍 Clipboard" \
"󰞅 Emoji" \
"󰹑 Screenshot" \
"󰸉 Wallpaper shuffle" \
"󰠹 Particles $(particles_status)" \
"󰍹 Bar $(bar_status)" \
"󰐥 Power" | menu "Control")
[ -z "$choice" ] && { CC_EXIT=1; return; }
case "$choice" in
󰖩*) wifi_menu ;;
󰂯*) bt_menu ;;
󰕾*) audio_menu ;;
󰍬*) have wpctl && wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
notify "Microphone" "Now $(mic_status)" ;;
󰖂*) vpn_menu ;;
󰍺*) display_menu ;;
󰂚*) notif_menu ;;
󰅍*) CC_EXIT=1; sh "$HOME/.config/scripts/rofi-menus/clipboard-manager.sh" ;;
󰞅*) CC_EXIT=1; sh "$HOME/.config/scripts/rofi-menus/rofimoji.sh" ;;
󰹑*) shot_menu ;;
󰸉*) sh "$HOME/.config/scripts/set-wallpaper.sh" >/dev/null 2>&1
notify "Wallpaper" "Shuffled" ;;
󰠹*) if have particles; then particles >/dev/null 2>&1
notify "Particles" "Now $(particles_status)"
else notify -u critical "Particles" "Not installed"; fi ;;
󰍹*) sh "$HOME/.config/scripts/toggle-bar.sh" ;;
󰐥*) power_menu ;;
esac
}
while [ "$CC_EXIT" -eq 0 ]; do
main_menu
done