#!/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" menu() { # menu [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@ 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") 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") 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") 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 ;; 󰅍*) sh "$HOME/.config/scripts/rofi-menus/clipboard-manager.sh" ;; 󰞅*) 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 } main_menu