]> git.sur5r.net Git - i3/i3status/blob - src/print_wireless_info.c
Properly output JSON with libyajl
[i3/i3status] / src / print_wireless_info.c
1 // vim:ts=8:expandtab
2 #include <stdio.h>
3 #include <string.h>
4
5 #ifdef LINUX
6 #include <iwlib.h>
7 #else
8 #ifndef __FreeBSD__
9 #define IW_ESSID_MAX_SIZE 32
10 #endif
11 #endif
12
13 #ifdef __FreeBSD__
14 #include <sys/param.h>
15 #include <sys/ioctl.h>
16 #include <sys/socket.h>
17 #include <ifaddrs.h>
18 #include <net/if.h>
19 #include <net/if_media.h>
20 #include <net80211/ieee80211.h>
21 #include <net80211/ieee80211_ioctl.h>
22 #include <unistd.h>
23 #define IW_ESSID_MAX_SIZE IEEE80211_NWID_LEN
24 #endif
25
26 #include "i3status.h"
27
28 #define WIRELESS_INFO_FLAG_HAS_ESSID                    (1 << 0)
29 #define WIRELESS_INFO_FLAG_HAS_QUALITY                  (1 << 1)
30 #define WIRELESS_INFO_FLAG_HAS_SIGNAL                   (1 << 2)
31 #define WIRELESS_INFO_FLAG_HAS_NOISE                    (1 << 3)
32
33 #define PERCENT_VALUE(value, total) ((int)(value * 100 / (float)total + 0.5f))
34
35 typedef struct {
36         int flags;
37         char essid[IW_ESSID_MAX_SIZE + 1];
38         int quality;
39         int quality_max;
40         int quality_average;
41         int signal_level;
42         int signal_level_max;
43         int noise_level;
44         int noise_level_max;
45         int bitrate;
46 } wireless_info_t;
47
48 static int get_wireless_info(const char *interface, wireless_info_t *info) {
49         memset(info, 0, sizeof(wireless_info_t));
50
51 #ifdef LINUX
52         int skfd = iw_sockets_open();
53         if (skfd < 0) {
54                 perror("iw_sockets_open");
55                 return 0;
56         }
57
58         wireless_config wcfg;
59         if (iw_get_basic_config(skfd, interface, &wcfg) < 0) {
60             close(skfd);
61             return 0;
62         }
63
64         if (wcfg.has_essid && wcfg.essid_on) {
65                 info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
66                 strncpy(&info->essid[0], wcfg.essid, IW_ESSID_MAX_SIZE);
67                 info->essid[IW_ESSID_MAX_SIZE] = '\0';
68         }
69
70         /* Wireless quality is a relative value in a driver-specific range.
71            Signal and noise level can be either relative or absolute values
72            in dBm. Furthermore, noise and quality can be expressed directly
73            in dBm or in RCPI (802.11k), which we convert to dBm. When those
74            values are expressed directly in dBm, they range from -192 to 63,
75            and since the values are packed into 8 bits, we need to perform
76            8-bit arithmetic on them. Assume absolute values if everything
77            else fails (driver bug). */
78
79         iwrange range;
80         if (iw_get_range_info(skfd, interface, &range) < 0) {
81                 close(skfd);
82                 return 0;
83         }
84
85         iwstats stats;
86         if (iw_get_stats(skfd, interface, &stats, &range, 1) < 0) {
87                 close(skfd);
88                 return 0;
89         }
90
91         if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
92                 if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
93                         info->quality = stats.qual.qual;
94                         info->quality_max = range.max_qual.qual;
95                         info->quality_average = range.avg_qual.qual;
96                         info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
97                 }
98
99                 if (stats.qual.updated & IW_QUAL_RCPI) {
100                         if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
101                                 info->signal_level = stats.qual.level / 2.0 - 110 + 0.5;
102                                 info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
103                         }
104                         if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
105                                 info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5;
106                                 info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
107                         }
108                 }
109                 else {
110                         if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) {
111                                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
112                                         info->signal_level = stats.qual.level;
113                                         if (info->signal_level > 63)
114                                                 info->signal_level -= 256;
115                                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
116                                 }
117                                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
118                                         info->noise_level = stats.qual.noise;
119                                         if (info->noise_level > 63)
120                                                 info->noise_level -= 256;
121                                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
122                                 }
123                         }
124                         else {
125                                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
126                                         info->signal_level = stats.qual.level;
127                                         info->signal_level_max = range.max_qual.level;
128                                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
129                                 }
130                                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
131                                         info->noise_level = stats.qual.noise;
132                                         info->noise_level_max = range.max_qual.noise;
133                                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
134                                 }
135                         }
136                 }
137         }
138         else {
139                 if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
140                         info->quality = stats.qual.qual;
141                         info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
142                 }
143                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
144                         info->quality = stats.qual.level;
145                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
146                 }
147                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
148                         info->quality = stats.qual.noise;
149                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
150                 }
151         }
152
153         struct iwreq wrq;
154         if (iw_get_ext(skfd, interface, SIOCGIWRATE, &wrq) >= 0)
155                 info->bitrate = wrq.u.bitrate.value;
156
157         close(skfd);
158         return 1;
159 #endif
160 #ifdef __FreeBSD__
161         int s, len, inwid;
162         uint8_t buf[24 * 1024], *cp;
163         struct ieee80211req na;
164         char network_id[IEEE80211_NWID_LEN + 1];
165
166         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
167                 return (0);
168
169         memset(&na, 0, sizeof(na));
170         strlcpy(na.i_name, interface, sizeof(na.i_name));
171         na.i_type = IEEE80211_IOC_SSID;
172         na.i_data = &info->essid[0];
173         na.i_len = IEEE80211_NWID_LEN + 1;
174         if ((inwid = ioctl(s, SIOCG80211, (caddr_t)&na)) == -1) {
175                 close(s);
176                 return (0);
177         }
178         if (inwid == 0) {
179                 if (na.i_len <= IEEE80211_NWID_LEN)
180                         len = na.i_len + 1;
181                 else
182                         len = IEEE80211_NWID_LEN + 1;
183                 info->essid[len -1] = '\0';
184         } else {
185                 close(s);
186                 return (0);
187         }
188         info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
189
190         memset(&na, 0, sizeof(na));
191         strlcpy(na.i_name, interface, sizeof(na.i_name));
192         na.i_type = IEEE80211_IOC_SCAN_RESULTS;
193         na.i_data = buf;
194         na.i_len = sizeof(buf);
195
196         if (ioctl(s, SIOCG80211, (caddr_t)&na) == -1) {
197                 printf("fail\n");
198                 close(s);
199                 return (0);
200         }
201
202         close(s);
203         len = na.i_len;
204         cp = buf;
205         struct ieee80211req_scan_result *sr;
206         uint8_t *vp;
207         sr = (struct ieee80211req_scan_result *)cp;
208         vp = (u_int8_t *)(sr + 1);
209         strlcpy(network_id, (const char *)vp, sr->isr_ssid_len + 1);
210         if (!strcmp(network_id, &info->essid[0])) {
211                 info->signal_level = sr->isr_rssi;
212                 info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
213                 info->noise_level = sr->isr_noise;
214                 info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
215                 info->quality = sr->isr_intval;
216                 info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
217         }
218
219         return 1;
220 #endif
221         return 0;
222 }
223
224 void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down) {
225         const char *walk;
226         char *outwalk = buffer;
227         wireless_info_t info;
228
229         INSTANCE(interface);
230
231         if (get_wireless_info(interface, &info)) {
232                 walk = format_up;
233                 if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY)
234                         START_COLOR((info.quality < info.quality_average ? "color_degraded" : "color_good"));
235         }
236         else {
237                 walk = format_down;
238                 START_COLOR("color_bad");
239         }
240
241         for (; *walk != '\0'; walk++) {
242                 if (*walk != '%') {
243                         *(outwalk++) = *walk;
244                         continue;
245                 }
246
247                 if (BEGINS_WITH(walk+1, "quality")) {
248                         if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) {
249                                 if (info.quality_max)
250                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.quality, info.quality_max));
251                                 else
252                                         outwalk += sprintf(outwalk, "%d", info.quality);
253                         } else {
254                                 *(outwalk++) = '?';
255                         }
256                         walk += strlen("quality");
257                 }
258
259                 if (BEGINS_WITH(walk+1, "signal")) {
260                         if (info.flags & WIRELESS_INFO_FLAG_HAS_SIGNAL) {
261                                 if (info.signal_level_max)
262                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.signal_level, info.signal_level_max));
263                                 else
264                                         outwalk += sprintf(outwalk, "%d dBm", info.signal_level);
265                         } else {
266                                 *(outwalk++) = '?';
267                         }
268                         walk += strlen("signal");
269                 }
270
271                 if (BEGINS_WITH(walk+1, "noise")) {
272                         if (info.flags & WIRELESS_INFO_FLAG_HAS_NOISE) {
273                                 if (info.noise_level_max)
274                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.noise_level, info.noise_level_max));
275                                 else
276                                         outwalk += sprintf(outwalk, "%d dBm", info.noise_level);
277                         } else {
278                                 *(outwalk++) = '?';
279                         }
280                         walk += strlen("noise");
281                 }
282
283                 if (BEGINS_WITH(walk+1, "essid")) {
284                         if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID)
285                                 outwalk += sprintf(outwalk, "%s", info.essid);
286                         else
287                                 *(outwalk++) = '?';
288                         walk += strlen("essid");
289                 }
290
291                 if (BEGINS_WITH(walk+1, "ip")) {
292                         const char *ip_address = get_ip_addr(interface);
293                         outwalk += sprintf(outwalk, "%s", (ip_address ? ip_address : "no IP"));
294                         walk += strlen("ip");
295                 }
296
297 #ifdef LINUX
298                 if (BEGINS_WITH(walk+1, "bitrate")) {
299                         char br_buffer[128];
300
301                         iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate);
302
303                         outwalk += sprintf(outwalk, "%s", br_buffer);
304                         walk += strlen("bitrate");
305                 }
306 #endif
307         }
308
309         END_COLOR;
310         OUTPUT_FULL_TEXT(buffer);
311 }