Skip to content

bluetooth: recover adapter power state from missed transitions - #1156

Open
neheb wants to merge 1 commit into
quickshell-mirror:masterfrom
neheb:fix/bluetooth-power-state-recovery
Open

neheb wants to merge 1 commit into
quickshell-mirror:masterfrom
neheb:fix/bluetooth-power-state-recovery

Conversation

@neheb

@neheb neheb commented Sep 17, 2026

Copy link
Copy Markdown

Summary

BluetoothAdapter.enabled / state can get permanently stuck on a transitional power state
(Enabling / Disabling) even though the adapter is actually powered and in use.

BlueZ can report an adapter as off-enabling / on-disabling without ever emitting the
PropertiesChanged that completes the transition, for example when a controller is
re-registered while it is still coming up (in the reproduction: a dev-id change from hci1
to hci0). Quickshell applies the transitional Powered=false snapshot and, with no follow-up
change ever arriving, keeps reporting the adapter as "off" forever.

Reproduced live on the omarchy shell (see omacom/omarchy#9561 for
the same bug reported from the shell side):

  • adapter hci0 was tracked with PowerState=Enabling / Powered=false during
    re-registration, and Powered=true was never applied afterwards;
  • yet bluetoothctl show reports the controller Powered: yes / PowerState: on, and a
    connected AirPods Pro is the default PipeWire sink and streaming audio.

Fix

When the adapter is observed in a transitional power state, re-read its properties via GetAll
until it settles on a terminal state (Enabled / Disabled / Blocked) or before that a
follow-up properties change arrives. Polling is bounded to 40 attempts at a 250 ms interval
(10 s ceiling) so it always terminates.

These are reads only (no Set), so the fix cannot interfere with setEnabled or other clients
toggling the adapter.

@outfoxxed

Copy link
Copy Markdown
Member

Please try to find a non polling way of doing this.

@outfoxxed
outfoxxed force-pushed the master branch 2 times, most recently from 72a1ce2 to 11ca60b Compare October 6, 2026 04:21
A new adapter's initial properties come from the InterfacesAdded snapshot,
but its PropertiesChanged match rule is only installed afterwards, when the
adapter object is constructed. Any change BlueZ emits in between is never
delivered, so an adapter registered while powering on or off (e.g. after a
controller swap) could stay stuck on a transitional PowerState forever.

Issue a single GetAll once the snapshot has been applied. It is ordered
after the match rule on the bus, so it picks up anything that was missed,
and every later change is guaranteed to arrive as a signal.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
@neheb
neheb force-pushed the fix/bluetooth-power-state-recovery branch from ff24918 to 93a822f Compare October 6, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants