-
-
Notifications
You must be signed in to change notification settings - Fork 899
Modules Wifi
Print connected Wi-Fi info (SSID, connection and security protocol)
| Module type | wifi |
| Default order | 52 (only used by --gen-config) |
| Module source | src/modules/wifi/wifi.c |
| Detection source | src/detection/wifi/ |
Prints one line per Wi-Fi interface. The printed key is Wi-Fi, and it is numbered when more than
one interface is present.
Wi-Fi: MyNetwork - 802.11ax (Wi-Fi 6) - 5 GHz - WPA2-PSK (80%)
The line is <ssid> - <protocol> - <band> - <security> followed by the signal quality in
parentheses. Fields that the platform did not report are dropped along with their separator, so a
2.4 GHz connection simply has no 2.4 GHz part. When there is no SSID to print — the interface is
down, or nothing is associated — the interface status takes the SSID's place instead:
Wi-Fi: Disconnected
The signal quality is the only part that follows display.percent.type; with the bar bit enabled
its bar is prepended to the whole line, and with hide-others everything except the bar and the
number is suppressed.
| Platform | Implementation | Notes |
|---|---|---|
| Linux | wifi_linux.c |
nl80211 over generic netlink, falling back to the wireless-extension ioctls |
| Android | wifi_android.c |
Talks to the wifi system service over raw binder and parses the returned parcel |
| FreeBSD | wifi_bsd.c |
SIOCG80211 ioctls on interfaces named wlan*
|
| NetBSD | wifi_nbsd.c |
Same ioctls, on interfaces named iwm*
|
| OpenBSD | wifi_obsd.c |
SIOCG80211NODE on every interface, filtering out the ones that reject it |
| Solaris / illumos | wifi_sunos.c |
/dev/wifi/* character devices, WLAN_GET_PARAM streams ioctls |
| Haiku | wifi_haiku.cpp |
BNetworkRoster plus BNetworkDevice
|
| macOS | wifi_apple.m |
CoreWLAN, with the frequency read from the IORegistry |
| Windows | wifi_windows.c |
WlanOpenHandle / WlanEnumInterfaces / WlanQueryInterface
|
| DragonFly | wifi_nosupport.c |
Reports Not supported on this platform
|
| GNU/Hurd | wifi_nosupport.c |
Same file |
DragonFly and GNU/Hurd share src/detection/wifi/wifi_nosupport.c, which returns a fixed error
message; every other platform has a real backend.
The module has no keys of its own.
| Key | Type | Default | Description |
|---|---|---|---|
percent |
object |
green: 75, yellow: 50
|
Colour thresholds for the signal quality |
key |
string | Wi-Fi |
Module key. A single space hides the key and the separator. |
keyColor |
color | – | Overrides display.color.keys
|
keyIcon |
string | built-in glyph | Printed when display.key.type includes the icon bit. Any glyph works; "" prints none. |
keyWidth |
integer | – | Overrides display.key.width
|
outputColor |
color | – | Overrides display.color.output
|
format |
string | – | Custom output format (see below) |
condition |
object | – | Show the module only if the conditions match |
The thresholds are inverted relative to most percentage modules: green: 75 is higher than
yellow: 50, which makes a strong signal green. With green > yellow the meaning is [green, 100]
green, [yellow, green) yellow and [0, yellow) red.
percent.type defaults to 0, which means "use display.percent.type" — by default the number and
its colour, no bar. The bits behave as they do everywhere else: num alone prints the number without
colour, num-color adds the colour, bar replaces the trailing (n%) with a bar in front of the
line, hide-others drops the SSID, protocol, band and security, and an empty list hides both the
number and the bar.
The bar is coloured from display.bar.color.elapsed / .total / .border, not from
display.percent.color. The elapsed section is split at the two thresholds, so with the default
green: 75 / yellow: 50 on a ten-cell bar the first five cells are red, the next three yellow and
the green section starts at cell eight. A bar whose width and thresholds make the green section fall
past the last filled cell shows no green at all, even though {signal-quality} is coloured green for
the same value — which is exactly what a 83% signal does with the defaults.
Run fastfetch -h wifi-format for the authoritative list.
| Variable | Description |
|---|---|
{inf-desc} |
Interface description |
{inf-status} |
Interface status |
{status} |
Connection status |
{ssid} |
Connection SSID |
{bssid} |
Connection BSSID |
{protocol} |
Connection protocol |
{signal-quality} |
Signal quality, formatted as a percentage |
{rx-rate} |
Receive rate in Mbit/s |
{tx-rate} |
Transmit rate in Mbit/s |
{security} |
Security algorithm |
{signal-quality-bar} |
Signal quality as a percentage bar |
{channel} |
Channel number |
{channel-width} |
Channel width in MHz |
{band} |
Channel band, as a bare number |
No variable carries the * marker, so nothing is available in the key format: key: "W[{ssid}]"
prints the literal text W[{ssid}] on every line.
{signal-quality} and {signal-quality-bar} are the only variables that are filled conditionally by
display.percent.type: the number is empty when the num bit is off and the bar is empty when the
bar bit is off. {band} holds the number only — 5, 2.4, 4.9, 3.65, 6, 45, 60, 0.9
or an empty string — while the default output appends GHz itself.
signalQuality, rxRate and txRate are null when the platform could not report them. All three
are stored as -DBL_MAX, but the two text paths render that sentinel differently:
{signal-quality} goes through the percentage formatter and becomes a single -, while {rx-rate}
and {tx-rate} are passed as raw doubles and become the literal -1.7976931348623157e308 — the
display.fraction settings do not apply, because the value is out of fixed-point range. The other
three connection numbers are 0 when unknown, because zero is a valid channel, width and frequency
for "nothing to report".
An interface that exists but is not connected still appears in the array, with an empty conn — the
text output prints inf.status in that case. The JSON writer does not check for an empty list, so a
machine without a Wi-Fi interface answers with "result": [] while the text path reports
No Wifi interfaces found.
// A bar before the SSID, plus the percentage
{ "type": "wifi", "percent": { "type": ["num", "bar", "num-color"] } }// Only the numbers, without the SSID and the protocol
{ "type": "wifi", "percent": { "type": ["num", "hide-others"] } }// One line per interface, in a fixed shape
{ "type": "wifi", "format": "{inf-desc} {ssid} {band}GHz ch{channel} {signal-quality}" }// Guard the fields that can be unknown
{ "type": "wifi", "format": "{ssid}{?tx-rate} @ {tx-rate} Mbit/s{?}" }-
{inf-status}and{status}are spelled differently on every platform. Linux writesup,down,unknown,connectedanddisconnectedin lower case; Android capitalises the interface state but not the connection state; Windows uses theWLAN_INTERFACE_STATEnames (Not ready,Connected,Disconnected,Associating,Authenticating,Discovering,Disconnecting,Ad hoc network formed); macOS usesPower On/Power OffandActive/Inactive; the BSDs useUp/Down/UnknownandAssociated/Not associated; Solaris and Haiku useUp/DownwithConnected/Not connected. A format that tests these strings only works on the platform it was written for. -
{status}is empty on Windows whenever the interface is not connected. The connection attributes are only queried after the interface state has been read asconnected, so a disconnected adapter has a filledinf.statusand an emptyconn.status. -
An unknown rate leaks a sentinel into a custom format.
{rx-rate}and{tx-rate}print-1.7976931348623157e308when the platform did not report them, because the variable carries the raw double. The JSON result writesnullfor the same state, and the percentage formatter renders its own sentinel as a plain-with a bar made of the "total" character. Guard the two rates with a conditional block, as in the example above. -
A conditional block does not guard
{signal-quality}, and does guard{channel-width}. The three sentinels are tested differently, so{?...}does not answer the same question for each:{?rx-rate}is false when the rate is unknown and when it is a real0, because a double counts as set only when it is> 0;{?channel-width}is false whenever the width is0, which on Windows is the ordinary case, so a guarded block around it silently disappears; and{?signal-quality}is true for an unknown signal, because the percentage formatter has already turned the sentinel into a non-empty-. An interface that is not connected therefore renderssq=-rather than dropping the block. -
{channel-width}can be0even when the platform answers. On Windows the value comes fromulBandwidthof the realtime-connection-quality query, and a driver that does not fill that field leaves it at0— a 160 MHz-capable adapter associated on a 160 MHz channel still reports0. The query succeeding and the width being present are two different things, so0must be read as "not reported" rather than as a measurement. -
{band}is a bare number. The default output addsGHz; a custom format that writes{band}GHzreproduces it, but one that writes{band}alone prints5. The value is derived from the frequency, not from the channel, and is empty when the frequency is unknown. -
The default line's shape cannot be reproduced with
{format}alone. The SSID, protocol, band and security parts are conditional and the percentage is parenthesised, and the parentheses are omitted when everything before it was suppressed — for example withhide-others. A custom format has to reimplement that logic. -
The line falls back to the interface status when the SSID is empty. This is why a disconnected
machine prints
Wi-Fi: Disconnectedand why a customformatthat only prints{ssid}renders an empty line there. -
The security strings are not portable. Windows reports
WPA2-PSK,WPA3-SAE,OWE,802.11X; macOS reportsWPA2 Personal,WPA3 Enterprise; Android reportsWPA2-PSK,WPA3-ENT-192,WAPI-PSK; Haiku reportsEncryptedfor an encrypted network with no authentication mode; the BSDs report the ioctl's own vocabulary (WPA2,WPA1+2,802.1x); Linux joins everything the beacon advertises into a/-separated list such asWPA2/WPA3.Insecureis the one spelling every backend agrees on. -
Linux changes its own answer depending on which transport worked. nl80211 is tried first and
the wireless-extension ioctls then fill only the fields that are still empty. When netlink is
unavailable — a kernel without
nl80211, a sandbox that blocks generic netlink — the security string degrades from the beacon-derived list to the negotiated cipher (WEP,TKIP,CCMP,PMK,CMAC), and{protocol}falls back to theSIOCGIWNAMEstring. -
The two rate variables are not both available on the BSDs. FreeBSD fills
{rx-rate}from the station'sisi_txmbpsand never sets{tx-rate}; NetBSD does the opposite, setting{tx-rate}fromisi_txrate. OpenBSD sets neither. -
{channel-width}is always0on OpenBSD and Solaris, and on Windows it is0whenever the driver leavesulBandwidthempty — see the dedicated bullet above.{channel}and{frequency}are filled from separate queries on Windows, so one can be present while the other is0. -
Linux only sees interfaces that register a
phy80211directory. The gate is/sys/class/net/<ifname>/phy80211/, so a driver that does not use mac80211 is invisible even though it is a Wi-Fi interface. Ifoperstatecannot be read at all, the interface is reported asunknown/disconnectedand neither transport is consulted. -
The signal quality is a percentage of different things. Most backends map RSSI with the same
piecewise rule (
-50 dBmor better is 100,-100 dBmor worse is 0, linear in between); OpenBSD divides the reported RSSI by the reported maximum, Solaris divides byMAX_RSSI, Haiku takes the driver's ownsignal_strengthclamped to 100, and Windows useswlanSignalQualityas-is. The numbers are comparable in range but not in meaning. -
Android reports at most one interface, and replaces an SSID or BSSID that the system service
withheld with
<redacted>rather than leaving it empty — an empty SSID is what the module prints the interface state in place of, and the reply still carries a signal, a channel and a rate that are worth showing. The same placeholder is used for a connection that genuinely has no address. -
The text path fails on an empty interface list but the JSON path does not. With no Wi-Fi
interface at all the text output is an error (
No Wifi interfaces found) and the JSON result is an empty array. This is the same split described inModules-Keyboard.
Every backend fills a list of FFWifiResult, each with an interface half (description, status)
and a connection half (status, ssid, bssid, protocol, security, signalQuality, rxRate,
txRate, channel, channelWidth, frequency). An interface that is present but not associated is
still added, with the connection fields left empty and the three numeric quality fields set to
-DBL_MAX. ffPrintWifi() then derives the band string from the frequency with fixed thresholds
(>58000 is 60, >40000 is 45, >5900 is 6, >5100 is 5, >4900 is 4.9, >3600 is
3.65, >2000 is 2.4, >800 is 0.9) and renders either the default line or the format string.
Interfaces are enumerated with if_nameindex() and filtered on the phy80211 directory. For an
interface whose operstate is up, detectWithNetlink() runs first: a generic netlink socket is
opened, the nl80211 family id is resolved through CTRL_CMD_GETFAMILY, the associated BSS is found
with a scan dump, and the interface and station dumps supply the protocol, the channel width, the
rates and the security from the RSN and WPA information elements. detectWithIoctl() then fills in
whatever is still unknown from SIOCGIWESSID, SIOCGIWNAME, SIOCGIWAP, SIOCGIWRATE,
SIOCGIWFREQ, SIOCGIWSTATS and SIOCGIWENCODEEXT. A failed netlink initialisation is latched in
a sentinel value so it is not retried per interface. The interface state for a non-up interface
comes from the IFF_UP bit of /sys/class/net/<ifname>/flags.
detectWithBinder() resolves the wifi service through the service manager, looks the transaction
code for getConnectionInfo up in the framework jar with the DEX parser, and calls it with the
process's own package name. The reply is a Java parcel, which is not self-describing, so the fields
are recognised by shape: a length-prefixed UTF-16 MAC address for the BSSID, one of the supplicant
state names for the state, an integer naming a Wi-Fi standard followed by two plausible link speeds
for the protocol, and an enum for the security type. The interface flags come from getifaddrs(),
falling back to getWifiEnabledState when the interface has no address — which is exactly the state
in which getifaddrs() omits it.
FreeBSD and NetBSD iterate if_nameindex() and filter on a name prefix (wlan, iwm), then use
IEEE80211_IOC_SSID, IEEE80211_IOC_BSSID, the current-channel request, IEEE80211_IOC_STA_INFO
and IEEE80211_IOC_AUTHMODE — NetBSD redefines a few IEEE80211_IS_CHAN_* macros because its
net80211 headers differ. OpenBSD has no name filter: it tries SIOCG80211NODE on every interface
and treats ENXIO, ENODEV, EINVAL and ENOTTY as "not a Wi-Fi interface", then reads
SIOCG80211CHANNEL, SIOCG80211WPAPARMS and SIOCG80211NWKEY.
/dev/wifi is walked for entries whose name ends in a digit, each device is opened and checked with
isastream(), and every value is fetched with a WLAN_GET_PARAM streams ioctl into a shared
wldp_t buffer: the radio state, the link status, the ESSID, the BSSID, the RSSI, the PHY
configuration and the encryption mode. A missing /dev/wifi is not an error — the function returns
success with an empty list.
BNetworkRoster::Default() enumerates the interfaces, BNetworkDevice::IsWireless() filters them,
and the associated network is read with GetNextAssociatedNetwork(). The protocol is never filled,
the channel and frequency are left at zero, and the BSSID comes from the network's link-level
address.
CWWiFiClient.sharedWiFiClient.interfaces provides the interface list. The SSID, BSSID and protocol
come from CoreWLAN, with the security enum mapped to names; since macOS redacts the SSID and BSSID
for an unprivileged caller, the module falls back to the saved network profile's SSID and BSSID list
and finally to the literal <redacted>. The signal quality is derived from the RSSI with the same
piecewise rule as Linux, and the frequency is read from the IO80211ChannelFrequency IORegistry
property, walking up the service plane until it is found.
WlanOpenHandle and WlanEnumInterfaces produce the interface list, and for each interface that is
connected the module queries the current connection for the SSID, BSSID, PHY type, signal quality,
rates and authentication algorithm. The channel width and centre frequency come from the
realtime-connection-quality query, which is not documented in the public SDK and is therefore
declared locally; the best link of an MLO connection is chosen by RSSI. If that query does not
produce a frequency, the BSS list is consulted instead — and when the list comes back empty the
frequency stays 0, which is why a custom format prints no band in that case. The channel number
comes from a separate wlan_intf_opcode_channel_number query. Every buffer the API hands out is
released with WlanFreeMemory, including a BSS list that succeeded with zero entries.
{ "type": "Wifi", "result": [ { "inf": { "description": "wlan0", "status": "up" }, "conn": { "status": "connected", "ssid": "MyNetwork", "bssid": "AA:BB:CC:DD:EE:FF", "protocol": "802.11ax (Wi-Fi 6)", "security": "WPA2/WPA3", "signalQuality": 78.0, "rxRate": 1200.0, "txRate": 866.7, "channel": 36, "channelWidth": 80, "frequency": 5180 } } ] }