aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/specs/2026-09-14-network-bluetooth-design.md160
1 files changed, 160 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-09-14-network-bluetooth-design.md b/docs/superpowers/specs/2026-09-14-network-bluetooth-design.md
new file mode 100644
index 0000000..53c79ac
--- /dev/null
+++ b/docs/superpowers/specs/2026-09-14-network-bluetooth-design.md
@@ -0,0 +1,160 @@
+# Network and Bluetooth modules for the desktop shell
+
+Two new modules under the `desktop/` drawer: `network`, covering wired and
+wifi, and `bluetooth`. They follow the module contract the desktop shell
+already defines, and they use the native Quickshell backends rather than
+shelling out to `nmcli` or `bluetoothctl`, with one deliberate exception for
+Bluetooth pairing.
+
+This is the first of the projects the desktop shell design named but left
+unbuilt. Do not disturb, breaktimer and kdeconnect remain later projects.
+
+## Why
+
+The drawer is where a desktop's adjustable things are visible together. Wifi
+and Bluetooth are the two most-used of those and are currently not in it. The
+design also chose to replace waybar's own network and Bluetooth indicators, so
+the drawer becomes the single place both are managed.
+
+## Scope
+
+In:
+
+- `network`: wired devices and wifi, radio switch, scan, join a network with a
+ password, join a hidden network, connect, disconnect, forget.
+- `bluetooth`: adapter power, discoverable, scan, pair, connect, disconnect,
+ forget, trust, device battery where reported.
+- Removing waybar's network and Bluetooth indicators from the live config.
+
+Out, and noted as ceilings:
+
+- Enterprise / 802.1x wifi. The profile model is far heavier than the drawer
+ wants, and nothing here uses it.
+- Joining a hidden network, if `NMSettings` turns out not to be constructible
+ in QML. Nothing else can create a profile from scratch, so this one is
+ conditional on a feasibility check during implementation. Dropped with a
+ note if it does not hold.
+
+## Backends
+
+Quickshell 0.3.1 ships the two APIs built in; verified against the 0.3.1
+reference and against the installed binary:
+
+- `Quickshell.Networking` (NetworkManager D-Bus). `Networking` has
+ `connectivity`, `devices`, `wifiEnabled`, `wifiHardwareEnabled`.
+ `NetworkDevice` has `autoconnect`, `name`, `address`, `networks`, `state`,
+ `type`, `connected`, `disconnect()`. `WifiDevice` adds `scannerEnabled` and
+ `mode`. `Network` has `known`, `nmSettings`, `state`, `connected`, `device`,
+ `name`, `connect()`, `connectWithSettings()`, `disconnect()`, `forget()` and
+ the `connectionFailed` signal. `WifiNetwork` adds `security`,
+ `signalStrength` and `connectWithPsk(psk)`.
+- `Quickshell.Bluetooth` (BlueZ D-Bus). `Bluetooth` has `adapters`,
+ `defaultAdapter`, `devices`. `BluetoothAdapter` has `enabled`,
+ `discoverable`, `discoverableTimeout`, `pairable`, `pairableTimeout`,
+ `discovering`, `devices`, `name`, `state`, `adapterId`. `BluetoothDevice`
+ has `paired`, `bonded`, `connected`, `trusted`, `blocked`, `battery`,
+ `batteryAvailable`, `icon`, `address`, `name`, `deviceName`, `state`,
+ `adapter`, and `pair()`, `cancelPair()`, `connect()`, `disconnect()`,
+ `forget()`.
+
+Because Quickshell has no generic D-Bus module and no BlueZ pairing agent,
+`pair()` cannot handle a device that requires a passkey or PIN confirmation.
+That one action shells out to `bluetoothctl`, whose own agent does.
+
+## Module shape
+
+Both modules are `alwaysActive: true` and declare it truthfully. There is no
+poll to gate: the backends are push, and the tile needs live state the moment
+the drawer opens. Unlike sound and vm, neither needs a `Service.qml`: there is
+no polling loop to own, so the module root references the singleton directly,
+which is also what instantiates it at shell start.
+
+```
+modules/network/ NetworkModule.qml NetworkTile.qml NetworkPage.qml NetworkRow.qml README.md
+modules/bluetooth/ BluetoothModule.qml BluetoothTile.qml BluetoothPage.qml BluetoothRow.qml Pairing.qml README.md
+```
+
+`shell.qml` gains the two imports and two registry entries. The drawer's
+`open(<name>)` IPC reaches the pages with no further wiring; the module names
+are `network` and `bluetooth`.
+
+Grid order becomes `Sound, Network, Bluetooth, Mail, Appearance, Machines`,
+six tiles, two rows of three in the 600px panel.
+
+## The network page
+
+**Wired.** The managed `WiredDevice`s only, filtered by `type` so `docker0`,
+`virbr0` and `veth*` never appear. Each is a row: interface, connected state,
+and disconnect.
+
+**Wifi.** A radio switch on `Networking.wifiEnabled`. Off hides the list and
+shows `Wi-Fi off`. On shows a scan toggle on `device.scannerEnabled` and a
+list ordered connected first, then known, then the rest by `signalStrength`.
+A lock glyph marks a secured network. Row click connects an open or known
+network directly; a secured unknown network reveals an inline password field
+on the row and submits through `connectWithPsk()`. A connected row offers
+Disconnect, a known row offers Forget.
+
+**Hidden network.** A `Join hidden network` row taking SSID, security and
+password, subject to the ceiling above.
+
+## The Bluetooth page
+
+Adapter power switch, then when on a `Make visible` toggle (`discoverable`)
+and a scan toggle (`discovering`). Connected devices come first, each with
+icon, name and battery when `batteryAvailable`, and click disconnects. Paired
+devices follow: connect, disconnect, forget, and a trust toggle. Devices found
+while scanning offer Pair, which starts `Pairing.qml`.
+
+## Tiles
+
+**Network.** The icon swaps wired, wifi, off. With one link up the state line
+names it (`eth0`, or the SSID). With both wired and wifi up the content is two
+compact lines, one each, because the API exposes no route metric and the tile
+will not guess which link carries traffic. `active` while any link is up.
+
+**Bluetooth.** The icon swaps on and off. The state line names the connected
+device, or a count when several, `No devices` when powered but idle, `Off`
+when the adapter is down. `active` while anything is connected.
+
+## Failures
+
+An inline status line on the page beside the affected row: a rejected PSK
+arrives as `connectionFailed`, and forgetting or pairing can fail too. A
+failure that lands after the drawer has closed also fires `notify-send`,
+using the cached-`Process` pattern already in `VmModule.qml`. The error clears
+on the next action.
+
+## Files outside this repo
+
+The live waybar configuration loses its network and Bluetooth indicators,
+applied by the user. Nothing else outside the repo changes: both pages render
+inside the existing `quickshell-desktop` window, so there is no new namespace
+and no new blur rule, and no new IPC.
+
+## Verification
+
+No automated oracle reaches a D-Bus backend, so verification is visual, by the
+user, following the AGENTS.md measurement rules (the process is `qs`, a
+detached one does not survive a tool call, `pkill -f` kills the caller):
+
+- radio toggles for both wifi and the Bluetooth adapter
+- join an open network and a secured one, once with a wrong PSK and once right
+- forget a network
+- wired shows connected; with wired and wifi both up the tile shows two lines
+- Bluetooth power, discoverable, scan
+- pair a Just Works device, then forget and re-trust it
+
+If `Pairing.qml` grows a parser for `bluetoothctl` output, that parser leaves
+one runnable `assert` check fed sample lines. `test-mail-notify.sh` is
+unaffected.
+
+## Traps carried forward
+
+- The PipeWire "initial value arrives before `ready`" pattern may apply to
+ these singletons too. Verify before trusting a first paint rather than
+ assuming a guard is needed.
+- QML's JS engine has no `String.matchAll`, so any `bluetoothctl` parsing uses
+ an `exec` loop.
+- `bluetoothctl` output parsing is brittle. Mark it with a `ponytail:` comment
+ naming the ceiling and the upgrade path.