Remember Bluetooth on/off through the rfkill soft block (#6682)
* Turn Bluetooth off with an rfkill soft block BlueZ never persists an adapter's Powered property, so turning Bluetooth off in the panel lasted only until the next boot. Omarchy's answer was AutoEnable=false, which persists nothing either — it just means "never power the adapter on", so Bluetooth came up off every boot whatever the user had chosen. The soft block already does the job. systemd-rfkill saves every switch under /var/lib/systemd/rfkill and restores it early on the next boot; that is the entire purpose of the unit. Blocking also covers every controller at once, where bluetoothctl only ever addresses the default one. So the block becomes the state and BlueZ follows it: with AutoEnable back at its stock default, lifting the block is enough for bluetoothd to power the adapter up on its own. Powered still tracks the block, so the panel switch and icon read it exactly as before. Everything that turns Bluetooth on or off goes through omarchy-bluetooth-power, because bluetoothctl power on fails while a block is set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Carry installed machines over to the rfkill block Existing installs have AutoEnable=false, so their adapter is down at every boot and Powered is the only record of what the user actually wants. Read it before anything changes, hand it to the block, then put AutoEnable back to its default so bluetoothd can act on that block. Only the exact line Omarchy wrote is reverted, so a hand-edited opt-out survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Ask the power helper for a direction, not a toggle The helper runs detached and the switch only moves once BlueZ catches up, so a second click inside that window re-read the pre-click state and undid the first. The panel already knows which way it wants to go, so let it say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read every controller and bound the power-up wait The block hits every Bluetooth radio at once, but the state was read from a bare bluetoothctl show, which reports the default controller only. A powered dongle sitting behind a powered-down internal controller read as off and got blocked along with it. Enumerate the controllers and take any powered one as on, exposed as is-on so callers do not each reinvent the read. The wait counted probes rather than time, so a wedged D-Bus turned a two-second bound into roughly fifty across a full power-up. One deadline around the whole wait holds it near nine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Change the radio through sudo in the migration /dev/rfkill is only writable unelevated from an active graphical seat, so an update run over SSH failed here with EACCES. Migrations run under bash -e, so that aborted before the config revert and the marker, and aborted again on every retry. The privilege guidance already calls for sudo on machine-wide work run from a visible terminal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
567e24cd90
commit
c53190be07
@@ -20,8 +20,9 @@ address=${2:-}
|
||||
|
||||
power_on() {
|
||||
[[ $(timeout 2s bluetoothctl show 2>/dev/null) == *"Powered: yes"* ]] && return
|
||||
bluetoothctl power on >/dev/null 2>&1 || true
|
||||
sleep 0.5
|
||||
# Not bluetoothctl directly: Bluetooth is turned off by an rfkill soft block,
|
||||
# and BlueZ refuses to power an adapter up while one is set.
|
||||
omarchy-bluetooth-power on || true
|
||||
}
|
||||
|
||||
trust_device() {
|
||||
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Turn Bluetooth on or off, remembered across reboots
|
||||
# omarchy:group=bluetooth
|
||||
# omarchy:args=<on|off|toggle|is-on>
|
||||
|
||||
# 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 <on|off|toggle|is-on>" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user