#!/bin/bash # omarchy:summary=Turn Bluetooth on or off, remembered across reboots # omarchy:group=bluetooth # omarchy:args= # BlueZ never persists an adapter's Powered property, so turning Bluetooth off # through bluetoothctl lasts only until the next boot. The rfkill soft block does # persist: systemd-rfkill saves every switch under /var/lib/systemd/rfkill and # restores it early on the next boot, which is its entire job. Blocking is also # what the kernel hands every radio at once, so a machine with two controllers # gets both, where bluetoothctl only ever addresses the default one. # # So the block is the state, and BlueZ follows it: unblocking leaves AutoEnable # at its stock default and bluetoothd powers the adapter up by itself. Every # Omarchy path that turns Bluetooth on or off goes through here, because a plain # `bluetoothctl power on` fails outright while the block is set. POWER_WAIT_SECONDS=${OMARCHY_BLUETOOTH_POWER_WAIT_SECONDS:-2} controllers() { timeout 2s bluetoothctl list 2>/dev/null | awk '{print $2}' } # Any controller counts. The block is all-or-nothing across the radios, so the # state has to be read the same way; a bare `bluetoothctl show` would report the # default controller and miss a powered dongle sitting behind it. powered() { local controller for controller in $(controllers); do [[ $(timeout 2s bluetoothctl show "$controller" 2>/dev/null) == *"Powered: yes"* ]] && return 0 done return 1 } # One deadline around the whole wait rather than a fixed number of probes: every # probe can sit on its own timeout when D-Bus is wedged, and counting probes then # stretches a two-second wait into half a minute. wait_powered() { local deadline=$((SECONDS + POWER_WAIT_SECONDS)) while :; do powered && return 0 ((SECONDS < deadline)) || return 1 sleep 0.2 done } power_on() { rfkill unblock bluetooth # Usually all it takes: with AutoEnable at its default, bluetoothd powers the # adapter up on its own once the block is gone. It will not do that for an # adapter powered down without a block, so ask directly before giving up. wait_powered && return 0 timeout 5s bluetoothctl power on >/dev/null 2>&1 wait_powered && return 0 echo "omarchy-bluetooth-power: adapter did not come up" >&2 return 1 } case "${1:-}" in on) power_on ;; off) # No bluetoothctl power off to go with this: the block already drops the # adapter to Powered: no, and it is the half that survives the reboot. rfkill block bluetooth ;; toggle) if powered; then rfkill block bluetooth else power_on fi ;; is-on) powered ;; *) echo "Usage: omarchy-bluetooth-power " >&2 exit 1 ;; esac