Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
Bluetooth bar widget disappears and cannot be toggled back on after rfkill-blocking a ThinkPad tpacpi switch
Reported by alhasapi · filed Sep 20, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- AreaOutside the audited area· Decided by agents · verified
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
Summary
On a ThinkPad, turning Bluetooth off from the Bluetooth bar widget makes the
widget disappear, and neither clicking where it used to be nor theSUPER+CTRL+B / IPC toggle can turn Bluetooth back on. Only the physicalFn radio button recovers it.
The cause is that omarchy-bluetooth-power off runs rfkill block bluetooth,
which also blocks the thinkpad_acpi master radio switch. On this hardware that
switch gates power to the internal USB Bluetooth module, so the module
de-enumerates and BlueZ unregisters the adapter entirely. Panel.qml then
treats "no adapter" as "no hardware": it hides the widget and makes its toggle a
no-op.
Environment
- Omarchy:
4.0.4-1 - Machine: Lenovo ThinkPad T480 (
20L50000MX) - Bluetooth module:
Bus 001 Device 051: ID 8087:0a2b Intel Corp. Bluetooth wireless interface, enumerated at USB port1-7
$ rfkill list # after turning Bluetooth off from the widget
0: tpacpi_bluetooth_sw: Bluetooth
Soft blocked: yes
Hard blocked: no
1: phy0: Wireless LAN
Soft blocked: no
Hard blocked: noRoot cause
omarchy-bluetooth-power implements off as:
rfkill block bluetoothBlocking the bluetooth rfkill type blocks every switch of that type, and
this ThinkPad exposes two:
platform-thinkpad_acpi:bluetooth(tpacpi_bluetooth_sw) — the master
switch, which also controls power to the module.
2. The internal USB module itself (pci-…-usb-0:7:1.0:bluetooth, USB port 1-7).
Blocking the thinkpad_acpi switch drops power to the USB module:
$ ls /sys/bus/usb/devices/ # while Bluetooth is on
… 1-7 1-7:1.0 1-7:1.1 …
$ ls /sys/bus/usb/devices/ # after `rfkill block bluetooth` settles
… (no 1-7) …
$ ls -A /sys/class/bluetooth/ # empty
$ bluetoothctl list # emptyBlueZ therefore unregisters the adapter, and Bluetooth.defaultAdapter becomesnull in the QML.
$OMARCHY_PATH/shell/plugins/panels/bluetooth/Panel.qml then hits two
adapter-null assumptions:
- Line 500 —
visible: adapter !== nullremoves the bar widget entirely,
and its icon (line 59, if (!adapter) return "") would render a zero-width
button anyway.
- Lines 636-637 — the toggle bails out:
function toggleBluetooth() {
if (!adapter) return
Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
} so the panel's switch (line 715, visible: !!root.adapter), the right-click
action, and the omarchy.bluetooth IPC handler all do nothing.
rfkill unblock bluetooth is exactly what brings the adapter back, and the
patched branch below can issue it while the adapter is absent.
Steps to reproduce
- On a ThinkPad (or any machine whose Bluetooth radio sits behind a second
rfkill switch of type bluetooth), click the Bluetooth bar widget and turn
it off (or run omarchy-bluetooth-power off).
2. Wait a few seconds for the internal module to power down.
3. The Bluetooth widget vanishes from the bar and /sys/class/bluetooth/ is empty.
4. Try SUPER+CTRL+B → toggle, or omarchy-shell omarchy.bluetooth toggleBluetooth:
nothing happens, the adapter stays gone.
5. Only the physical Fn radio button (which flips tpacpi_bluetooth_sw back to
soft=0 and re-powers the module) restores Bluetooth.
Suggested fix
Treat "no adapter" as "Bluetooth is off" rather than "no hardware", so the
widget stays reachable and its toggle can re-enable the radio. Diff againstshell/plugins/panels/bluetooth/Panel.qml:
--- a/shell/plugins/panels/bluetooth/Panel.qml
+++ b/shell/plugins/panels/bluetooth/Panel.qml
@@ -56,7 +56,7 @@
readonly property var discoveredDevices: deviceGroups.discovered || []
readonly property string icon: {
- if (!adapter) return ""
+ if (!adapter) return ""
if (!adapter.enabled) return ""
if (connectedDevices.length > 0) return ""
return ""
@@ -75,7 +75,7 @@
]
readonly property bool rotatingPhrases: adapter && adapter.enabled
readonly property string heroStatusText: {
- if (!adapter) return "No adapter"
+ if (!adapter) return "Turned Off"
if (!adapter.enabled) return "Turned Off"
return activePhrases[phraseIndex % activePhrases.length]
}
@@ -497,7 +497,7 @@
if (selectedIndex < 0) selectedIndex = 0
}
- visible: adapter !== null
+ visible: true
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
@@ -633,8 +633,10 @@
// switch only moves once BlueZ catches up, so a second click inside that window
// would re-read the old state and undo the first.
function toggleBluetooth() {
- if (!adapter) return
- Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
+ if (adapter)
+ Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
+ else
+ Quickshell.execDetached(["omarchy-bluetooth-power", "on"])
}
IpcHandler {
@@ -712,7 +714,7 @@
// header's only cursor target.
ToggleSwitch {
id: powerSwitch
- visible: !!root.adapter
+ visible: true
checked: !!root.adapter && root.adapter.enabled
hasCursor: root.headerHasCursor
foreground: root.bar.foreground
@@ -867,7 +869,7 @@
Text {
textFormat: Text.PlainText
visible: root.connectedDevices.length === 0 && root.scrollRows.length === 0
- text: !root.adapter ? "No Bluetooth adapter"
+ text: !root.adapter ? "Bluetooth is off"
: !root.adapter.enabled ? "Turn Bluetooth on to scan"
: "Scanning for devices…"
color: Qt.darker(root.bar.foreground, 1.5)With this applied, the widget stays in the bar showing the off glyph, and
toggling it while the adapter is absent runs omarchy-bluetooth-power on
(rfkill unblock bluetooth), which powers the module back up and restores the
adapter. All other adapter-null guards in the panel (discovery retry, theConnections targets, onOpenedChanged, device sections) already tolerate a
null adapter and need no change.
Tradeoff: on a machine with no Bluetooth hardware at all, the widget now shows an
always-off icon instead of being hidden. That seemed the better default, but aBluetooth.adapters.length === 0 hardware check could be used to hide it in that
case if preferred.
Verification
Applied locally as a cloned plugin (~/.config/omarchy/plugins/…-bluetooth/),
with Bluetooth off and /sys/class/bluetooth/ empty:
$ omarchy-shell omarchy.bluetooth toggleBluetooth # adapter is null
$ rfkill list
0: tpacpi_bluetooth_sw: Bluetooth
Soft blocked: no
$ ls /sys/class/bluetooth/
hci0and toggling with the adapter present still runs omarchy-bluetooth-power off
as before. A full shell restart was required for the clone's edit to take effect;
the local-plugin hot-reload logged Local plugin changed, reloading but the
running panel kept the pre-edit code (worth a separate look).
View raw text
## Summary
On a ThinkPad, turning Bluetooth **off** from the Bluetooth bar widget makes the
widget disappear, and neither clicking where it used to be nor the
`SUPER+CTRL+B` / IPC toggle can turn Bluetooth back on. Only the physical
`Fn` radio button recovers it.
The cause is that `omarchy-bluetooth-power off` runs `rfkill block bluetooth`,
which also blocks the `thinkpad_acpi` master radio switch. On this hardware that
switch gates power to the internal USB Bluetooth module, so the module
de-enumerates and BlueZ unregisters the adapter entirely. `Panel.qml` then
treats "no adapter" as "no hardware": it hides the widget and makes its toggle a
no-op.
## Environment
- Omarchy: `4.0.4-1`
- Machine: Lenovo ThinkPad T480 (`20L50000MX`)
- Bluetooth module: `Bus 001 Device 051: ID 8087:0a2b Intel Corp. Bluetooth wireless interface`, enumerated at USB port `1-7`
```
$ rfkill list # after turning Bluetooth off from the widget
0: tpacpi_bluetooth_sw: Bluetooth
Soft blocked: yes
Hard blocked: no
1: phy0: Wireless LAN
Soft blocked: no
Hard blocked: no
```
## Root cause
`omarchy-bluetooth-power` implements off as:
```bash
rfkill block bluetooth
```
Blocking the `bluetooth` rfkill **type** blocks every switch of that type, and
this ThinkPad exposes two:
1. `platform-thinkpad_acpi:bluetooth` (`tpacpi_bluetooth_sw`) — the master
switch, which also controls power to the module.
2. The internal USB module itself (`pci-…-usb-0:7:1.0:bluetooth`, USB port `1-7`).
Blocking the `thinkpad_acpi` switch drops power to the USB module:
```
$ ls /sys/bus/usb/devices/ # while Bluetooth is on
… 1-7 1-7:1.0 1-7:1.1 …
$ ls /sys/bus/usb/devices/ # after `rfkill block bluetooth` settles
… (no 1-7) …
$ ls -A /sys/class/bluetooth/ # empty
$ bluetoothctl list # empty
```
BlueZ therefore unregisters the adapter, and `Bluetooth.defaultAdapter` becomes
`null` in the QML.
`$OMARCHY_PATH/shell/plugins/panels/bluetooth/Panel.qml` then hits two
adapter-null assumptions:
- **Line 500** — `visible: adapter !== null` removes the bar widget entirely,
and its `icon` (line 59, `if (!adapter) return ""`) would render a zero-width
button anyway.
- **Lines 636-637** — the toggle bails out:
```js
function toggleBluetooth() {
if (!adapter) return
Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
}
```
so the panel's switch (line 715, `visible: !!root.adapter`), the right-click
action, and the `omarchy.bluetooth` IPC handler all do nothing.
`rfkill unblock bluetooth` is exactly what brings the adapter back, and the
patched branch below can issue it while the adapter is absent.
## Steps to reproduce
1. On a ThinkPad (or any machine whose Bluetooth radio sits behind a second
`rfkill` switch of type `bluetooth`), click the Bluetooth bar widget and turn
it off (or run `omarchy-bluetooth-power off`).
2. Wait a few seconds for the internal module to power down.
3. The Bluetooth widget vanishes from the bar and `/sys/class/bluetooth/` is empty.
4. Try `SUPER+CTRL+B` → toggle, or `omarchy-shell omarchy.bluetooth toggleBluetooth`:
nothing happens, the adapter stays gone.
5. Only the physical `Fn` radio button (which flips `tpacpi_bluetooth_sw` back to
`soft=0` and re-powers the module) restores Bluetooth.
## Suggested fix
Treat "no adapter" as "Bluetooth is off" rather than "no hardware", so the
widget stays reachable and its toggle can re-enable the radio. Diff against
`shell/plugins/panels/bluetooth/Panel.qml`:
```diff
--- a/shell/plugins/panels/bluetooth/Panel.qml
+++ b/shell/plugins/panels/bluetooth/Panel.qml
@@ -56,7 +56,7 @@
readonly property var discoveredDevices: deviceGroups.discovered || []
readonly property string icon: {
- if (!adapter) return ""
+ if (!adapter) return ""
if (!adapter.enabled) return ""
if (connectedDevices.length > 0) return ""
return ""
@@ -75,7 +75,7 @@
]
readonly property bool rotatingPhrases: adapter && adapter.enabled
readonly property string heroStatusText: {
- if (!adapter) return "No adapter"
+ if (!adapter) return "Turned Off"
if (!adapter.enabled) return "Turned Off"
return activePhrases[phraseIndex % activePhrases.length]
}
@@ -497,7 +497,7 @@
if (selectedIndex < 0) selectedIndex = 0
}
- visible: adapter !== null
+ visible: true
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
@@ -633,8 +633,10 @@
// switch only moves once BlueZ catches up, so a second click inside that window
// would re-read the old state and undo the first.
function toggleBluetooth() {
- if (!adapter) return
- Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
+ if (adapter)
+ Quickshell.execDetached(["omarchy-bluetooth-power", adapter.enabled ? "off" : "on"])
+ else
+ Quickshell.execDetached(["omarchy-bluetooth-power", "on"])
}
IpcHandler {
@@ -712,7 +714,7 @@
// header's only cursor target.
ToggleSwitch {
id: powerSwitch
- visible: !!root.adapter
+ visible: true
checked: !!root.adapter && root.adapter.enabled
hasCursor: root.headerHasCursor
foreground: root.bar.foreground
@@ -867,7 +869,7 @@
Text {
textFormat: Text.PlainText
visible: root.connectedDevices.length === 0 && root.scrollRows.length === 0
- text: !root.adapter ? "No Bluetooth adapter"
+ text: !root.adapter ? "Bluetooth is off"
: !root.adapter.enabled ? "Turn Bluetooth on to scan"
: "Scanning for devices…"
color: Qt.darker(root.bar.foreground, 1.5)
```
With this applied, the widget stays in the bar showing the off glyph, and
toggling it while the adapter is absent runs `omarchy-bluetooth-power on`
(`rfkill unblock bluetooth`), which powers the module back up and restores the
adapter. All other adapter-null guards in the panel (discovery retry, the
`Connections` targets, `onOpenedChanged`, device sections) already tolerate a
null adapter and need no change.
Tradeoff: on a machine with no Bluetooth hardware at all, the widget now shows an
always-off icon instead of being hidden. That seemed the better default, but a
`Bluetooth.adapters.length === 0` hardware check could be used to hide it in that
case if preferred.
## Verification
Applied locally as a cloned plugin (`~/.config/omarchy/plugins/…-bluetooth/`),
with Bluetooth off and `/sys/class/bluetooth/` empty:
```
$ omarchy-shell omarchy.bluetooth toggleBluetooth # adapter is null
$ rfkill list
0: tpacpi_bluetooth_sw: Bluetooth
Soft blocked: no
$ ls /sys/class/bluetooth/
hci0
```
and toggling with the adapter present still runs `omarchy-bluetooth-power off`
as before. A full shell restart was required for the clone's edit to take effect;
the local-plugin hot-reload logged `Local plugin changed, reloading` but the
running panel kept the pre-edit code (worth a separate look).
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Out of scopeDecided by agents · verified · 2026-10-09 16:23 UTC