|
4 | 4 |
|
5 | 5 | #import <CoreWLAN/CoreWLAN.h> |
6 | 6 |
|
| 7 | +static inline double rssiToSignalQuality(int rssi) |
| 8 | +{ |
| 9 | + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); |
| 10 | +} |
| 11 | + |
7 | 12 | static bool queryIpconfig(const char* ifName, FFstrbuf* result) |
8 | 13 | { |
| 14 | + if (@available(macOS 26.0, *)) |
| 15 | + { |
| 16 | + // ipconfig no longer work in Tahoe |
| 17 | + return false; |
| 18 | + } |
| 19 | + |
9 | 20 | return ffProcessAppendStdOut(result, (char* const[]) { |
10 | 21 | "/usr/sbin/ipconfig", |
11 | 22 | "getsummary", |
@@ -41,15 +52,194 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb |
41 | 52 | return true; |
42 | 53 | } |
43 | 54 |
|
44 | | -const char* ffDetectWifi(FFlist* result) |
| 55 | +static const char* detectByWdutil(FFlist* result) |
| 56 | +{ |
| 57 | + FF_STRBUF_AUTO_DESTROY wdutil = ffStrbufCreate(); |
| 58 | + |
| 59 | + if (geteuid() != 0) |
| 60 | + return "wdutil requires root privileges to run"; |
| 61 | + |
| 62 | + bool ok = ffProcessAppendStdOut(&wdutil, (char* const[]) { |
| 63 | + "/usr/bin/wdutil", |
| 64 | + "info", |
| 65 | + NULL |
| 66 | + }) == NULL; |
| 67 | + if (!ok) return "Failed to run wdutil info command"; |
| 68 | + |
| 69 | + // ffStrbufSetS(&wdutil, "————————————————————————————————————————————————————————————————————\nNETWORK\n————————————————————————————————————————————————————————————————————\n Primary IPv4 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 192.168.48.96\n Primary IPv6 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n DNS Addresses : 240a:42a3:5200:1092::1b\n : 192.168.48.210\n Apple : Reachable\n————————————————————————————————————————————————————————————————————\nWIFI\n————————————————————————————————————————————————————————————————————\n MAC Address : 9e:c4:0a:4b:fe:ab (hw=f4:d4:88:70:f3:39)\n Interface Name : en0\n Power : On [On]\n Op Mode : STA\n SSID : iQOO 13\n BSSID : 92:ad:bb:24:51:bb\n RSSI : -24 dBm\n CCA : 36 %\n Noise : -83 dBm\n Tx Rate : 229.0 Mbps\n Security : WPA2 Personal\n PHY Mode : 11ax\n MCS Index : 9\n Guard Interval : 800\n NSS : 2\n Channel : 2g6/20\n Country Code : CN\n Scan Cache Count : 37\n NetworkServiceID : 37C2D473-40B7-4AFB-93D2-49C588580986\n IPv4 Config Method : DHCP\n IPv4 Address : 192.168.48.96\n IPv4 Router : 192.168.48.210\n IPv6 Config Method : Automatic\n IPv6 Address : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n IPv6 Router : fe80::90ad:bbff:fe24:51bb\n DNS : 192.168.48.210\n : 240a:42a3:5200:1092::1b\n BTC Mode : Off\n Desense :\n Chain Ack : []\n BTC Profile 2.4GHz : Disabled\n BTC Profile 5GHz : Disabled\n Sniffer Supported : YES\n Supports 6e : No\n Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80\n————————————————————————————————————————————————————————————————————\nBLUETOOTH\n————————————————————————————————————————————————————————————————————\n Power : On\n Address : f4:d4:88:78:91:4a\n Discoverable : No\n Connectable : Yes\n Scanning : No\n Devices : 12 (paired=5 cloud=1 connected=0)\n\n iQOO Wireless Active\n Address : 20:18:5b:3a:fe:93\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:ac\n Paired : No\n CloudPaired : No\n Connected : No\n\n M585/M590\n Address : c9:7a:ff:82:3b:fe\n Paired : No\n CloudPaired : No\n Connected : No\n\n Xiaomi Earphones Explore\n Address : e0:08:71:78:75:0d\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n WH-1000XM5\n Address : 88:c9:e8:61:fa:76\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER G501\n Address : d1:00:0f:08:ae:42\n Paired : No\n CloudPaired : No\n Connected : No\n\n MSI BluetoothMouse\n Address : e0:10:9c:40:73:c5\n Paired : No\n CloudPaired : No\n Connected : No\n\n Bluetooth Mouse M336/M337/M535\n Address : 34:88:5d:8f:c0:fe\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n MacBook Pro\n Address : 3c:15:c2:e5:00:b7\n Paired : No\n CloudPaired : Yes\n Connected : No\n\n\n Address : e4:57:68:8e:bb:28\n Paired : No\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:aa\n Paired : No\n CloudPaired : No\n Connected : No\n\n Pro Controller\n Address : 5c:52:1e:88:7e:70\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n————————————————————————————————————————————————————————————————————\nAWDL\n————————————————————————————————————————————————————————————————————\n MAC Address : 46:3a:d9:c1:b0:2e (hw=46:3a:d9:c1:b0:2e)\n AirDrop Disc Mode : Everyone\n AWDL Enabled : No\n Interface Name : awdl0\n Power : On\n IPv6 Address : fe80::443a:d9ff:fec1:b02e\n Schedule State : n/a\n Channel Sequence : n/a\n Op Mode : n/a\n Real Time Mode : No\n Sync State : n/a\n Sync Params : n/a\n Master Channel : n/a\n Election Params : n/a\n————————————————————————————————————————————————————————————————————\nPOWER\n————————————————————————————————————————————————————————————————————\n Power Source : AC\n Battery Warning Level: None\n System Caps : FullWake:cpu disk net aud vid\n————————————————————————————————————————————————————————————————————\nWIFI FAULTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI RECOVERIES LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI LINK TESTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None"); |
| 70 | + |
| 71 | + // ... |
| 72 | + // ———————————————————————————————————————————————————————————————————— |
| 73 | + // WIFI |
| 74 | + // ———————————————————————————————————————————————————————————————————— |
| 75 | + // <WIFI INFO> |
| 76 | + // ———————————————————————————————————————————————————————————————————— |
| 77 | + // ... |
| 78 | + |
| 79 | + { |
| 80 | + // Remove unrelated lines |
| 81 | + uint32_t start = ffStrbufFirstIndexS(&wdutil, "\nWIFI\n"); |
| 82 | + if (start >= wdutil.length) |
| 83 | + return "wdutil info command did not return WIFI section (1)"; |
| 84 | + |
| 85 | + start += 6; // Skip "\nWIFI\n" |
| 86 | + start = ffStrbufNextIndexC(&wdutil, start, '\n'); |
| 87 | + if (start >= wdutil.length) |
| 88 | + return "wdutil info command did not return WIFI section (2)"; |
| 89 | + start++; |
| 90 | + |
| 91 | + uint32_t end = ffStrbufNextIndexS(&wdutil, start, "\n——————————"); |
| 92 | + |
| 93 | + ffStrbufSubstr(&wdutil, start, end); |
| 94 | + } |
| 95 | + |
| 96 | + |
| 97 | + // `wdutil info <interface>` returns a string like this: |
| 98 | + // MAC Address : xx:xx:xx:xx:xx:xx (hw=xx:xx:xx:xx:xx:xx) |
| 99 | + // Interface Name : en0 |
| 100 | + // Power : On [On] |
| 101 | + // Op Mode : STA |
| 102 | + // SSID : XXX-XXX |
| 103 | + // BSSID : xx:xx:xx:xx:xx:xx |
| 104 | + // RSSI : -58 dBm |
| 105 | + // CCA : 29 % |
| 106 | + // Noise : -96 dBm |
| 107 | + // Tx Rate : 173.0 Mbps |
| 108 | + // Security : WPA2 Enterprise |
| 109 | + // 802.1X Mode : User |
| 110 | + // 802.1X Supplicant : Authenticated |
| 111 | + // PHY Mode : 11ac |
| 112 | + // MCS Index : 8 |
| 113 | + // Guard Interval : 400 |
| 114 | + // NSS : 2 |
| 115 | + // Channel : 5g165/20 |
| 116 | + // Country Code : CN |
| 117 | + // Scan Cache Count : 28 |
| 118 | + // NetworkServiceID : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |
| 119 | + // IPv4 Config Method : DHCP |
| 120 | + // IPv4 Address : xx.xx.xx.xx |
| 121 | + // IPv4 Router : xx.xx.xx.x |
| 122 | + // IPv6 Config Method : Automatic |
| 123 | + // IPv6 Address : xxxx:xxxx:xxxx:xxxx:xxxx:xxxx |
| 124 | + // IPv6 Router : None |
| 125 | + // DNS : xxx.xxx.xxx.xxx |
| 126 | + // : xxx.xxx.xxx.xxx |
| 127 | + // BTC Mode : Off |
| 128 | + // Desense : |
| 129 | + // Chain Ack : [] |
| 130 | + // BTC Profile 2.4GHz : Disabled |
| 131 | + // BTC Profile 5GHz : Disabled |
| 132 | + // Sniffer Supported : YES |
| 133 | + // Supports 6e : No |
| 134 | + // Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80 |
| 135 | + |
| 136 | + FFWifiResult* item = (FFWifiResult*) ffListAdd(result); |
| 137 | + ffStrbufInit(&item->inf.description); |
| 138 | + ffStrbufInit(&item->inf.status); |
| 139 | + ffStrbufInit(&item->conn.status); |
| 140 | + ffStrbufInit(&item->conn.ssid); |
| 141 | + ffStrbufInit(&item->conn.bssid); |
| 142 | + ffStrbufInit(&item->conn.protocol); |
| 143 | + ffStrbufInit(&item->conn.security); |
| 144 | + item->conn.signalQuality = 0.0/0.0; |
| 145 | + item->conn.rxRate = 0.0/0.0; |
| 146 | + item->conn.txRate = 0.0/0.0; |
| 147 | + item->conn.channel = 0; |
| 148 | + item->conn.frequency = 0; |
| 149 | + |
| 150 | + char* line = NULL; |
| 151 | + size_t len = 0; |
| 152 | + while (ffStrbufGetline(&line, &len, &wdutil)) |
| 153 | + { |
| 154 | + const char* key = line + 4; // Skip " " |
| 155 | + const char* value = key + strlen("MAC Address : "); |
| 156 | + switch (key[0] << 24 | key[1] << 16 | key[2] << 8 | key[3]) |
| 157 | + { |
| 158 | + case 'Inte': // Interface Name |
| 159 | + ffStrbufAppendS(&item->inf.description, value); |
| 160 | + break; |
| 161 | + case 'Powe': // Power |
| 162 | + if (ffStrStartsWith(value, "On ")) |
| 163 | + ffStrbufSetStatic(&item->inf.status, "Power On"); |
| 164 | + else |
| 165 | + ffStrbufSetStatic(&item->inf.status, "Power Off"); |
| 166 | + break; |
| 167 | + case 'SSID': // SSID |
| 168 | + ffStrbufAppendS(&item->conn.ssid, value); |
| 169 | + break; |
| 170 | + case 'BSSI': // BSSID |
| 171 | + if (ffStrEquals(value, "None") && ffStrbufEqualS(&item->conn.ssid, "None")) |
| 172 | + { |
| 173 | + ffStrbufSetStatic(&item->conn.status, "Inactive"); |
| 174 | + ffStrbufClear(&item->conn.ssid); // None |
| 175 | + return NULL; |
| 176 | + } |
| 177 | + ffStrbufSetStatic(&item->conn.status, "Active"); |
| 178 | + ffStrbufAppendS(&item->conn.bssid, value); |
| 179 | + break; |
| 180 | + case 'RSSI': // RSSI |
| 181 | + item->conn.signalQuality = rssiToSignalQuality((int) strtol(value, NULL, 10)); |
| 182 | + break; |
| 183 | + case 'Tx R': // Tx Rate |
| 184 | + item->conn.txRate = strtod(value, NULL); |
| 185 | + break; |
| 186 | + case 'Secu': // Security |
| 187 | + if (ffStrEquals(value, "None")) |
| 188 | + ffStrbufSetStatic(&item->conn.security, "Insecure"); |
| 189 | + else |
| 190 | + ffStrbufAppendS(&item->conn.security, value); |
| 191 | + break; |
| 192 | + case 'PHY ': // PHY Mode |
| 193 | + if (ffStrEquals(value, "None")) |
| 194 | + ffStrbufSetStatic(&item->conn.protocol, "none"); |
| 195 | + else if (ffStrEquals(value, "11a")) |
| 196 | + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); |
| 197 | + else if (ffStrEquals(value, "11b")) |
| 198 | + ffStrbufSetStatic(&item->conn.protocol, "802.11b"); |
| 199 | + else if (ffStrEquals(value, "11g")) |
| 200 | + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); |
| 201 | + else if (ffStrEquals(value, "11n")) |
| 202 | + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); |
| 203 | + else if (ffStrEquals(value, "11ac")) |
| 204 | + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); |
| 205 | + else if (ffStrEquals(value, "11ax")) |
| 206 | + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); |
| 207 | + else if (ffStrEquals(value, "11be")) |
| 208 | + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); |
| 209 | + else |
| 210 | + ffStrbufAppendS(&item->conn.protocol, value); |
| 211 | + break; |
| 212 | + case 'Chan': // Channel |
| 213 | + { |
| 214 | + int band, channel; |
| 215 | + if (sscanf(value, "%dg%d", &band, &channel) == 2) |
| 216 | + { |
| 217 | + item->conn.channel = (uint16_t) channel; |
| 218 | + switch (band) |
| 219 | + { |
| 220 | + case 2: item->conn.frequency = 2400; break; |
| 221 | + case 5: item->conn.frequency = 5400; break; |
| 222 | + case 6: item->conn.frequency = 6400; break; |
| 223 | + default: item->conn.frequency = 0; break; |
| 224 | + } |
| 225 | + } |
| 226 | + break; |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + return NULL; |
| 232 | +} |
| 233 | + |
| 234 | +static const char* detectByCoreWlan(FFlist* result) |
45 | 235 | { |
46 | 236 | NSArray<CWInterface*>* interfaces = CWWiFiClient.sharedWiFiClient.interfaces; |
47 | 237 | if (!interfaces) |
48 | 238 | return "CWWiFiClient.sharedWiFiClient.interfaces is nil"; |
49 | 239 |
|
50 | 240 | for (CWInterface* inf in interfaces) |
51 | 241 | { |
52 | | - FFWifiResult* item = (FFWifiResult*)ffListAdd(result); |
| 242 | + FFWifiResult* item = (FFWifiResult*) ffListAdd(result); |
53 | 243 | ffStrbufInit(&item->inf.description); |
54 | 244 | ffStrbufInit(&item->inf.status); |
55 | 245 | ffStrbufInit(&item->conn.status); |
@@ -79,12 +269,14 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb |
79 | 269 | else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) |
80 | 270 | getWifiInfoByIpconfig(&ipconfig, "\n SSID : ", &item->conn.ssid); |
81 | 271 | else |
82 | | - ffStrbufSetStatic(&item->conn.ssid, "<unknown ssid>"); // https://developer.apple.com/forums/thread/732431 |
| 272 | + ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431 |
83 | 273 |
|
84 | 274 | if (inf.bssid) |
85 | 275 | ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String); |
86 | 276 | else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) |
87 | 277 | getWifiInfoByIpconfig(&ipconfig, "\n BSSID : ", &item->conn.bssid); |
| 278 | + else |
| 279 | + ffStrbufSetStatic(&item->conn.bssid, "<redacted>"); |
88 | 280 |
|
89 | 281 | switch(inf.activePHYMode) |
90 | 282 | { |
@@ -117,7 +309,7 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb |
117 | 309 | ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode); |
118 | 310 | break; |
119 | 311 | } |
120 | | - item->conn.signalQuality = (double) (inf.rssiValue >= -50 ? 100 : inf.rssiValue <= -100 ? 0 : (inf.rssiValue + 100) * 2); |
| 312 | + item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue); |
121 | 313 | item->conn.txRate = inf.transmitRate; |
122 | 314 |
|
123 | 315 | switch(inf.security) |
@@ -189,5 +381,17 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb |
189 | 381 | default: item->conn.frequency = 0; break; |
190 | 382 | } |
191 | 383 | } |
| 384 | + |
192 | 385 | return NULL; |
193 | 386 | } |
| 387 | + |
| 388 | +const char* ffDetectWifi(FFlist* result) |
| 389 | +{ |
| 390 | + if (@available(macOS 26.0, *)) |
| 391 | + { |
| 392 | + if (detectByWdutil(result) == NULL) |
| 393 | + return NULL; |
| 394 | + } |
| 395 | + |
| 396 | + return detectByCoreWlan(result); |
| 397 | +} |
0 commit comments