#!/usr/bin/env bash # rice-bt - bluetooth state for the eww panel, and the actions it runs. # # rice-bt list JSON: power, discovery, paired and nearby devices # rice-bt power on|off|toggle # rice-bt scan look for new devices for 15 seconds # rice-bt toggle connect or disconnect; new devices are paired and trusted first # rice-bt forget # # Device names are chosen by the device, so the panel only ever passes MAC # addresses, and every MAC is checked before it is used. set -uo pipefail run="${XDG_RUNTIME_DIR:-/tmp}/rice" mkdir -p "$run" say() { rice-notify send "$1" "${2:-}" -i bluetooth; } poke() { date +%s%N > "$run/bt.rev"; } mac_ok() { [[ ${1:-} =~ ^([0-9A-F]{2}:){5}[0-9A-F]{2}$ ]] || { echo "rice-bt: not a MAC address: '${1:-}'" >&2; exit 2; }; } powered() { bluetoothctl show 2>/dev/null | grep -q 'Powered: yes'; } glyph() { case "$1" in audio-headset|audio-headphones) echo 󰋋 ;; audio-card) echo 󰓃 ;; input-keyboard) echo 󰌌 ;; input-mouse) echo 󰍽 ;; input-gaming) echo 󰊴 ;; phone) echo 󰏲 ;; computer) echo 󰍹 ;; *) echo 󰂯 ;; esac } device_json() { local mac="$1" info name icon paired connected battery info=$(bluetoothctl info "$mac" 2>/dev/null) name=$(sed -n 's/^\s*Alias: //p' <<<"$info" | head -n1) icon=$(sed -n 's/^\s*Icon: //p' <<<"$info" | head -n1) paired=$(grep -q 'Paired: yes' <<<"$info" && echo true || echo false) connected=$(grep -q 'Connected: yes' <<<"$info" && echo true || echo false) battery=$(sed -n 's/^\s*Battery Percentage: .*(\([0-9]*\))/\1/p' <<<"$info" | head -n1) jq -cn --arg mac "$mac" --arg name "${name:-$mac}" --arg glyph "$(glyph "$icon")" \ --argjson paired "$paired" --argjson connected "$connected" --arg battery "$battery" \ '{mac: $mac, name: $name, glyph: $glyph, paired: $paired, connected: $connected, battery: (if $battery == "" then "" else $battery + "%" end), sub: (if $connected then "connected" elif $paired then "paired" else "tap to pair" end)}' } list() { local on=false discovering=false paired="[]" nearby="[]" mac if powered; then on=true bluetoothctl show 2>/dev/null | grep -q 'Discovering: yes' && discovering=true paired=$(bluetoothctl devices Paired 2>/dev/null | awk '{ print $2 }' | grep -E '^([0-9A-F]{2}:){5}[0-9A-F]{2}$' | while read -r mac; do device_json "$mac"; done | jq -cs 'sort_by(.connected | not)') nearby=$(comm -23 <(bluetoothctl devices 2>/dev/null | awk '{ print $2 }' | sort) \ <(bluetoothctl devices Paired 2>/dev/null | awk '{ print $2 }' | sort) | grep -E '^([0-9A-F]{2}:){5}[0-9A-F]{2}$' | head -n 8 | while read -r mac; do device_json "$mac"; done | jq -cs '[.[] | select(.name != .mac)]') fi jq -cn --argjson powered "$on" --argjson discovering "$discovering" \ --argjson paired "$paired" --argjson nearby "$nearby" \ '{powered: $powered, discovering: $discovering, paired: $paired, nearby: $nearby, connected: ([$paired[] | select(.connected)] | length)}' } case "${1:-}" in list) list ;; power) case "${2:-toggle}" in on) rfkill unblock bluetooth 2>/dev/null; bluetoothctl power on >/dev/null ;; off) bluetoothctl power off >/dev/null ;; *) if powered; then bluetoothctl power off >/dev/null; else rfkill unblock bluetooth 2>/dev/null; bluetoothctl power on >/dev/null; fi ;; esac poke ;; scan) powered || bluetoothctl power on >/dev/null setsid -f bluetoothctl --timeout 15 scan on >/dev/null 2>&1 poke ;; toggle) mac_ok "${2:-}"; mac="$2" info=$(bluetoothctl info "$mac" 2>/dev/null) name=$(sed -n 's/^\s*Alias: //p' <<<"$info" | head -n1) if grep -q 'Connected: yes' <<<"$info"; then bluetoothctl disconnect "$mac" >/dev/null && say "Disconnected" "$name" else if ! grep -q 'Paired: yes' <<<"$info"; then bluetoothctl --timeout 20 pair "$mac" >/dev/null 2>&1 || { say "Pairing failed" "$name"; exit 1; } bluetoothctl trust "$mac" >/dev/null fi bluetoothctl --timeout 15 connect "$mac" >/dev/null 2>&1 && say "Connected" "$name" || say "Could not connect" "$name" fi poke ;; forget) mac_ok "${2:-}" bluetoothctl remove "$2" >/dev/null && say "Forgot device" "$2" poke ;; *) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; esac