bluetooth
BlueZ: adapter power, discovery, connected, paired and discovered devices, and pairing prompts.
list {
source = mantle.bluetooth:map(function(bluetooth)
return bluetooth and bluetooth.connected_devices or {}
end),
key = function(device) return device.mac end,
itemfn = function(device)
local battery = device.battery >= 0 and string.format(" %d%%", device.battery) or ""
return button {
on_click = function() mantle.bluetooth:disconnect(device.mac) end,
children = { text { content = device.name .. battery } },
}
end,
}
State
mantle.bluetooth:get() returns BluetoothState, nil before the first push. A field marked ? may be absent.
| Field | Type | Description |
|---|---|---|
available | boolean | BlueZ has an adapter; false without one or without bluetoothd. |
connected_devices | ConnectedDevice[] | Paired, connected devices. Unordered and may reshuffle on any push: sort before drawing. |
discoverable | boolean | Other devices can find this adapter. BlueZ turns it off after DiscoverableTimeout (180 s by default). |
discovered_devices | DiscoveredDevice[] | Unpaired devices BlueZ knows, unordered. Kept after stop_discovery; BlueZ expires unseen temporary ones after TemporaryTimeout (30 s by default). |
discovering | boolean | The adapter is scanning, whichever client started it. |
enabled | boolean | The adapter is powered. |
paired_devices | PairedDevice[] | Paired devices that are not connected. Unordered like connected_devices. |
pairing_request? | PairingRequest | The pairing question to show, or nil. Answer with answer_pairing. |
ConnectedDevice
| Field | Type | Description |
|---|---|---|
battery | integer | Battery percentage, or -1 when the device reports none. |
busy? | DeviceAction | Same as DiscoveredDevice::busy. |
category | string | From the class of device: "keyboard", "mouse", "headphones", "headset", "phone", "computer" or "generic". |
mac | string | MAC address, e.g. "00:1A:7D:DA:71:11"; every bluetooth action takes it. |
name | string | The device’s advertised name, or empty. |
DeviceAction
What the shell is doing to a device, as its busy.
One of "pairing", "connecting", "disconnecting".
DiscoveredDevice
| Field | Type | Description |
|---|---|---|
blocked | boolean | BlueZ refuses to pair with or connect to the device until it is unblocked. |
busy? | DeviceAction | The action this shell is running on the device, or nil; another client’s never shows. |
mac | string | MAC address, the argument of pair. |
name | string | Advertised name, often empty when the device broadcasts only an address. |
paired | boolean | Always false. |
PairedDevice
| Field | Type | Description |
|---|---|---|
blocked | boolean | BlueZ refuses every connection to or from the device until it is unblocked. |
busy? | DeviceAction | Same as DiscoveredDevice::busy. |
category | string | Same set as ConnectedDevice.category. |
mac | string | MAC address, the argument of connect and forget. |
name | string | The device’s advertised name, or empty. |
PairingKind
What a pairing_request asks; see PairingRequest.kind.
One of "confirm", "authorize", "service", "display".
PairingRequest
What the pairing agent is asking the user.
| Field | Type | Description |
|---|---|---|
code? | string | Six-digit passkey for "confirm", passkey or PIN for "display", else nil. |
kind | PairingKind | "confirm": does the device show code? "authorize": may it pair? "service": may a paired, untrusted device connect? "display": type code on the device; nothing to answer. |
mac | string | The device’s MAC address. |
name | string | The device’s advertised name, or empty. |
Actions
Call each as mantle.bluetooth:<action>(arguments...); ? marks an argument you may omit.
| Action | Arguments | Description |
|---|---|---|
set_enabled | enabled: boolean | Powers the adapter on or off. |
set_discoverable | discoverable: boolean | Makes the adapter findable by other devices, or not. |
start_discovery | Clears discovered_devices and scans. The request holds, so a scan starts once the adapter powers on and pauses while a pair runs. | |
stop_discovery | Stops discovery; discovered_devices stays. | |
pair | mac: string | Pairs a discovered device, then trusts and connects it. |
connect | mac: string | Trusts and connects a paired device. |
disconnect | mac: string | Disconnects a connected device. |
forget | mac: string | Removes a device from BlueZ, unpairing it. |
answer_pairing | mac: string, accept: boolean | Accepts or rejects the pairing_request for mac; a yes within 750 ms of it appearing is ignored. |
Backend
BlueZ on the system bus.
| Contract | Behavior |
|---|---|
| State | org.bluez’s ObjectManager plus property changes. An adapter added later is picked up; a bluetoothd started after the Supervisor is not. Without BlueZ, available is false, the lists stay empty and every action does nothing |
| Agent | Mantle registers the default DisplayYesNo agent at /org/mantle/Bluez/Agent1. A confirmation, authorization or displayed code becomes pairing_request only while the adapter is discoverable or Mantle is pairing that device. A "service" request asks only for a paired device. One request shows at a time: a second is rejected, unless the first only displays a code and the second needs an answer. PIN and passkey entry are rejected |
| Discovery | start_discovery clears discovered_devices; stop_discovery keeps it |
| Battery, category | Battery1 gives battery; the Class major and minor bits give category |
| Codecs | PipeWire owns them: audio.bluetooth lists each device’s profiles and set_bluetooth_profile switches one |
Gotchas
| Trap | Fix |
|---|---|
| A device pairing from its own side gets rejected | Mantle only prompts for invited devices. Set set_discoverable to true while pairing |
| A device that needs a PIN typed on the computer fails to pair | The agent rejects PIN and passkey entry. Pair it with bluetoothctl, which brings its own agent |