radio
rfkill: soft and hard block state per radio kind; nil without /dev/rfkill.
Toggle airplane mode with one call, and show which radios are blocked:
rect {
on_click = function()
local radio = mantle.radio:get()
if radio then
local blocked = false
for _, r in ipairs(radio.radios) do
blocked = blocked or r.soft_blocked
end
mantle.radio:set_all_blocked(not blocked)
end
end,
children = {
text {
content = mantle.radio:map(function(radio)
for _, r in ipairs(radio.radios) do
if r.hard_blocked then return "airplane (switch)" end
end
return "radios"
end, ""),
},
},
}
rfkill is the airplane layer: set_blocked and set_all_blocked flip the kernel’s soft block, which switches the radios themselves off. network.set_wifi_enabled and bluetooth.set_enabled are separate and unchanged. There is no airplane field: compute it from radios.
State
mantle.radio:get() returns RadioState, nil before the first push. A field marked ? may be absent.
mantle.radio’s payload; the capability stays nil without /dev/rfkill or until a radio exists.
| Field | Type | Description |
|---|---|---|
radios | Radio[] | One entry per kind with at least one device, ordered by kernel type id. |
Radio
One radio class and the rfkill block state of its devices.
| Field | Type | Description |
|---|---|---|
hard_blocked | boolean | Hardware switch block, which software cannot clear: any device of this kind is blocked. |
kind | RadioKind | |
soft_blocked | boolean | Software block, which set_blocked changes: any device of this kind is blocked. |
RadioKind
A radio class, named as the kernel’s rfkill type; discriminants are the kernel type ids.
One of "wlan", "bluetooth", "uwb", "wimax", "wwan", "gps", "fm", "nfc".
Actions
Call each as mantle.radio:<action>(arguments...); ? marks an argument you may omit.
| Action | Arguments | Description |
|---|---|---|
set_blocked | kind: RadioKind, blocked: boolean | Soft-blocks or unblocks every device of kind. A hardware block stays. |
set_all_blocked | blocked: boolean | Soft-blocks or unblocks every radio (airplane mode). |
Backend
| Contract | Behavior |
|---|---|
| Source | /dev/rfkill only: one event per existing device at start, then live changes. No polling |
| Kinds | One entry per kind with a device, ordered by kernel type id. A kind counts as blocked when any of its devices is. Unknown kernel types are skipped |
| No device node | Stays nil for good; actions are logged and ignored |
| No radios | Stays nil until one appears: the kernel sends nothing to read |
| Writes | One CHANGE_ALL request to the same fd; the kernel also applies it to radios plugged in later. State updates when the kernel reports the change |
Gotchas
| Trap | Fix |
|---|---|
set_blocked(kind, false) leaves hard_blocked = true | A hardware switch overrides software. Only the switch clears it; show it instead |
A session without a seat cannot write /dev/rfkill | State is still reported; actions are logged and ignored |