Conversation
`krypt menu bluetooth` opened bluetui in a floating terminal: not a picker, a new window per click, and unlike the pikr menus it needed a config file just to close on Esc. `.menu-wifi` already solved the same shape of task in pikr, so Bluetooth now follows it — same debounce, toggle and lock, same rows-file pick mapping, same degradation on a pikr without --kb-custom or --loading. Known devices list at once with no scan; discovery runs only from a "Scan for new devices" row. Reads are one busctl GetManagedObjects snapshot shaped by jq, and every change is confirmed by re-reading the property rather than trusting an exit status. Every action has a row as well as a key, so the menu is whole for a mouse and for stock pikr. Pairing covers Just Works only. A device that needs a passkey or PIN is handed to bluetui, which stays the fallback; docs/backlog.md records that and what has not been run against real hardware.
Devices and actions shared one list, which read as a list of things to connect to with three odd entries in it. The actions are now keys named in the prompt, following bluetui's letters where pikr leaves them free, and a pikr that cannot bind them gets a row through to a list of its own instead. A mouse on a pikr that can bind them reaches connect and disconnect only; that trade and its alternatives are in the backlog. Pairing never stored a key. bluetoothctl's one-shot `pair` asks bluez to pair before its own agent is registered, so bluez fell back to NoInputNoOutput and negotiated No Bonding, which is by definition never persisted: AirPods Pro reported Paired: yes, played audio, and dropped back to Paired: no seconds later. An HCI trace showed our Pair Device carrying Capability: NoInputNoOutput where bluetui's carried KeyboardDisplay. The agent session is now driven over a named pipe and the pairing waits for bluez to accept it as the default agent first. A device that is connected but not paired -- forgotten here while it still holds its own key, so it reconnects unasked -- offered Disconnect as its only action, and reconnected immediately after, with no way out of the picker. It offers pairing now, and the link it opened is dropped first so the pairing is ours to initiate rather than one we inherit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
krypt menu bluetoothis the odd one out on the bar. It launches bluetui in afloating alacritty, so it is not a picker, it stacks a new window per click
(no lock, no debounce), and it needed a config file of its own just to close on
Esc.
.menu-wifialready solved the same shape of problem — list, pick,connect, forget — in pikr. This ports Bluetooth to that model.
How it works
Reads are one
busctl GetManagedObjectssnapshot per refresh, shaped byjq;bash only splits on tab. Connect, disconnect, forget and power are
busctlcalls, and each is verified by re-reading the property rather than trusting the
exit status — "already connected" and "in progress" are errors whose outcome is
success. Only scanning and pairing need
bluetoothctl, because both need aprocess that stays alive: discovery belongs to the D-Bus client that asked for
it, and an agent lives only as long as its process.
Devices and actions are separate lists. The actions are keys, named in the
prompt, using bluetui's letters where pikr leaves them free —
^Sscan,^Opower; bluetui's
ufor unpair is pikr's clear-line, so forget isRight, asin
.menu-wifi. A pikr without--kb-customhas no keys to name, so there thedevice list carries a row through to an actions list instead; the script probes
pikr --helprather than assuming.Pairing
Worth calling out, because it was not obvious.
bluetoothctl --agent X pairinits one-shot form asks bluez to pair before its own agent is registered, so
bluez falls back to
NoInputNoOutputand negotiates No Bonding — a keythat is never persisted. AirPods Pro reported
Paired: yes, played audio, anddropped to
Paired: noseconds later, repeatably. An HCI trace showed ourPair DevicecarryingCapability: NoInputNoOutputwhere bluetui's carriedKeyboardDisplay. The agent session is now driven over a named pipe, and thepairing waits for bluez to confirm the default agent before asking.
Anything needing a passkey or a PIN still hands off to bluetui with a
notification; bluetui stays in
deps.tomlas that fallback.Verification
Stub harness (fake
busctl,bluetoothctl,pikr,notify-sendonPATHwith canned D-Bus JSON): 137 assertions, 0 failing, and every guard was
broken once to confirm its test goes red.
shellcheck -xclean at defaultseverity with zero directives; mode
100755.On real hardware (AirPods Pro 2, Keychron K8): pairing, bonding, forgetting and
reconnecting, confirmed both in
/var/lib/bluetoothand in an HCI trace.The power toggle and the forget-list path are covered by the harness but have
not been run on a live adapter.
Deliberate trade-off
With a pikr that can bind the keys, a mouse reaches connect and disconnect
and nothing else — pikr makes only a row clickable, so a mouse route to the
actions means either action rows in the device list or a second binding on the
bar icon, and both were rejected here. This is a known departure from "mouse
and keyboard, both work, always"; the reasoning and what would have to give to
revisit it are written up in
docs/backlog.md, along with the rest of what isnot covered.