]> git.sur5r.net Git - i3/i3status/blob - src/print_battery_info.c
clang-format-3.5 -i **/*.[ch], update modeline
[i3/i3status] / src / print_battery_info.c
1 // vim:ts=4:sw=4:expandtab
2 #include <ctype.h>
3 #include <time.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <yajl/yajl_gen.h>
8 #include <yajl/yajl_version.h>
9
10 #include "i3status.h"
11
12 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
13 #include <sys/types.h>
14 #include <sys/sysctl.h>
15 #endif
16
17 #if defined(__OpenBSD__)
18 #include <sys/types.h>
19 #include <sys/ioctl.h>
20 #include <sys/fcntl.h>
21 #include <machine/apmvar.h>
22 #endif
23
24 #if defined(__NetBSD__)
25 #include <fcntl.h>
26 #include <prop/proplib.h>
27 #include <sys/envsys.h>
28 #endif
29
30 /*
31  * Get battery information from /sys. Note that it uses the design capacity to
32  * calculate the percentage, not the last full capacity, so you can see how
33  * worn off your battery is.
34  *
35  */
36 void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char *path, const char *format, const char *format_down, const char *status_chr, const char *status_bat, const char *status_full, int low_threshold, char *threshold_type, bool last_full_capacity, bool integer_battery_capacity, bool hide_seconds) {
37     time_t empty_time;
38     struct tm *empty_tm;
39     char buf[1024];
40     char statusbuf[16];
41     char percentagebuf[16];
42     char remainingbuf[256];
43     char emptytimebuf[256];
44     char consumptionbuf[256];
45     const char *walk, *last;
46     char *outwalk = buffer;
47     bool watt_as_unit = false;
48     bool colorful_output = false;
49     int full_design = -1,
50         remaining = -1,
51         present_rate = -1,
52         voltage = -1;
53     charging_status_t status = CS_DISCHARGING;
54
55     memset(statusbuf, '\0', sizeof(statusbuf));
56     memset(percentagebuf, '\0', sizeof(percentagebuf));
57     memset(remainingbuf, '\0', sizeof(remainingbuf));
58     memset(emptytimebuf, '\0', sizeof(emptytimebuf));
59     memset(consumptionbuf, '\0', sizeof(consumptionbuf));
60
61     static char batpath[512];
62     sprintf(batpath, path, number);
63     INSTANCE(batpath);
64
65 #define BATT_STATUS_NAME(status) \
66     (status == CS_CHARGING ? status_chr : (status == CS_DISCHARGING ? status_bat : status_full))
67
68 #if defined(LINUX)
69     if (!slurp(batpath, buf, sizeof(buf))) {
70         OUTPUT_FULL_TEXT(format_down);
71         return;
72     }
73
74     for (walk = buf, last = buf; (walk - buf) < 1024; walk++) {
75         if (*walk == '\n') {
76             last = walk + 1;
77             continue;
78         }
79
80         if (*walk != '=')
81             continue;
82
83         if (BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_NOW")) {
84             watt_as_unit = true;
85             remaining = atoi(walk + 1);
86         } else if (BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_NOW")) {
87             watt_as_unit = false;
88             remaining = atoi(walk + 1);
89         } else if (BEGINS_WITH(last, "POWER_SUPPLY_CURRENT_NOW"))
90             present_rate = abs(atoi(walk + 1));
91         else if (BEGINS_WITH(last, "POWER_SUPPLY_VOLTAGE_NOW"))
92             voltage = abs(atoi(walk + 1));
93         /* on some systems POWER_SUPPLY_POWER_NOW does not exist, but actually
94          * it is the same as POWER_SUPPLY_CURRENT_NOW but with μWh as
95          * unit instead of μAh. We will calculate it as we need it
96          * later. */
97         else if (BEGINS_WITH(last, "POWER_SUPPLY_POWER_NOW"))
98             present_rate = abs(atoi(walk + 1));
99         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Charging"))
100             status = CS_CHARGING;
101         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Full"))
102             status = CS_FULL;
103         else {
104             /* The only thing left is the full capacity */
105             if (last_full_capacity) {
106                 if (!BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL") &&
107                     !BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL"))
108                     continue;
109             } else {
110                 if (!BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL_DESIGN") &&
111                     !BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL_DESIGN"))
112                     continue;
113             }
114
115             full_design = atoi(walk + 1);
116         }
117     }
118
119     /* the difference between POWER_SUPPLY_ENERGY_NOW and
120      * POWER_SUPPLY_CHARGE_NOW is the unit of measurement. The energy is
121      * given in mWh, the charge in mAh. So calculate every value given in
122      * ampere to watt */
123     if (!watt_as_unit) {
124         present_rate = (((float)voltage / 1000.0) * ((float)present_rate / 1000.0));
125
126         if (voltage != -1) {
127             remaining = (((float)voltage / 1000.0) * ((float)remaining / 1000.0));
128             full_design = (((float)voltage / 1000.0) * ((float)full_design / 1000.0));
129         }
130     }
131
132     if ((full_design == -1) || (remaining == -1)) {
133         OUTPUT_FULL_TEXT(format_down);
134         return;
135     }
136
137     (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status));
138
139     float percentage_remaining = (((float)remaining / (float)full_design) * 100);
140     if (integer_battery_capacity) {
141         (void)snprintf(percentagebuf, sizeof(percentagebuf), "%.00f%%", percentage_remaining);
142     } else {
143         (void)snprintf(percentagebuf, sizeof(percentagebuf), "%.02f%%", percentage_remaining);
144     }
145
146     if (present_rate > 0) {
147         float remaining_time;
148         int seconds, hours, minutes, seconds_remaining;
149         if (status == CS_CHARGING)
150             remaining_time = ((float)full_design - (float)remaining) / (float)present_rate;
151         else if (status == CS_DISCHARGING)
152             remaining_time = ((float)remaining / (float)present_rate);
153         else
154             remaining_time = 0;
155
156         seconds_remaining = (int)(remaining_time * 3600.0);
157
158         hours = seconds_remaining / 3600;
159         seconds = seconds_remaining - (hours * 3600);
160         minutes = seconds / 60;
161         seconds -= (minutes * 60);
162
163         if (status == CS_DISCHARGING && low_threshold > 0) {
164             if (strcasecmp(threshold_type, "percentage") == 0 && percentage_remaining < low_threshold) {
165                 START_COLOR("color_bad");
166                 colorful_output = true;
167             } else if (strcasecmp(threshold_type, "time") == 0 && seconds_remaining < 60 * low_threshold) {
168                 START_COLOR("color_bad");
169                 colorful_output = true;
170             } else {
171                 colorful_output = false;
172             }
173         }
174
175         if (hide_seconds)
176             (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02d:%02d",
177                            max(hours, 0), max(minutes, 0));
178         else
179             (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02d:%02d:%02d",
180                            max(hours, 0), max(minutes, 0), max(seconds, 0));
181
182         empty_time = time(NULL);
183         empty_time += seconds_remaining;
184         empty_tm = localtime(&empty_time);
185
186         if (hide_seconds)
187             (void)snprintf(emptytimebuf, sizeof(emptytimebuf), "%02d:%02d",
188                            max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0));
189         else
190             (void)snprintf(emptytimebuf, sizeof(emptytimebuf), "%02d:%02d:%02d",
191                            max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0));
192
193         (void)snprintf(consumptionbuf, sizeof(consumptionbuf), "%1.2fW",
194                        ((float)present_rate / 1000.0 / 1000.0));
195     } else {
196         /* On some systems, present_rate may not exist. Still, make sure
197          * we colorize the output if threshold_type is set to percentage
198          * (since we don't have any information on remaining time). */
199         if (status == CS_DISCHARGING && low_threshold > 0) {
200             if (strcasecmp(threshold_type, "percentage") == 0 && percentage_remaining < low_threshold) {
201                 START_COLOR("color_bad");
202                 colorful_output = true;
203             }
204         }
205     }
206 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
207     int state;
208     int sysctl_rslt;
209     size_t sysctl_size = sizeof(sysctl_rslt);
210
211     if (sysctlbyname(BATT_LIFE, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
212         OUTPUT_FULL_TEXT(format_down);
213         return;
214     }
215
216     present_rate = sysctl_rslt;
217     if (sysctlbyname(BATT_TIME, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
218         OUTPUT_FULL_TEXT(format_down);
219         return;
220     }
221
222     remaining = sysctl_rslt;
223     if (sysctlbyname(BATT_STATE, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
224         OUTPUT_FULL_TEXT(format_down);
225         return;
226     }
227
228     state = sysctl_rslt;
229     if (state == 0 && present_rate == 100)
230         status = CS_FULL;
231     else if (state == 0 && present_rate < 100)
232         status = CS_CHARGING;
233     else
234         status = CS_DISCHARGING;
235
236     full_design = sysctl_rslt;
237
238     (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status));
239
240     (void)snprintf(percentagebuf, sizeof(percentagebuf), "%02d%%",
241                    present_rate);
242
243     if (state == 1) {
244         int hours, minutes;
245         minutes = remaining;
246         hours = minutes / 60;
247         minutes -= (hours * 60);
248         (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02dh%02d",
249                        max(hours, 0), max(minutes, 0));
250         if (strcasecmp(threshold_type, "percentage") == 0 && present_rate < low_threshold) {
251             START_COLOR("color_bad");
252             colorful_output = true;
253         } else if (strcasecmp(threshold_type, "time") == 0 && remaining < (u_int)low_threshold) {
254             START_COLOR("color_bad");
255             colorful_output = true;
256         }
257     }
258 #elif defined(__OpenBSD__)
259     /*
260          * We're using apm(4) here, which is the interface to acpi(4) on amd64/i386 and
261          * the generic interface on macppc/sparc64/zaurus, instead of using sysctl(3) and
262          * probing acpi(4) devices.
263          */
264     struct apm_power_info apm_info;
265     int apm_fd;
266
267     apm_fd = open("/dev/apm", O_RDONLY);
268     if (apm_fd < 0) {
269         OUTPUT_FULL_TEXT("can't open /dev/apm");
270         return;
271     }
272     if (ioctl(apm_fd, APM_IOC_GETPOWER, &apm_info) < 0)
273         OUTPUT_FULL_TEXT("can't read power info");
274
275     close(apm_fd);
276
277     /* Don't bother to go further if there's no battery present. */
278     if ((apm_info.battery_state == APM_BATTERY_ABSENT) ||
279         (apm_info.battery_state == APM_BATT_UNKNOWN)) {
280         OUTPUT_FULL_TEXT(format_down);
281         return;
282     }
283
284     switch (apm_info.ac_state) {
285         case APM_AC_OFF:
286             status = CS_DISCHARGING;
287             break;
288         case APM_AC_ON:
289             status = CS_CHARGING;
290             break;
291         default:
292             /* If we don't know what's going on, just assume we're discharging. */
293             status = CS_DISCHARGING;
294             break;
295     }
296
297     (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status));
298     /* integer_battery_capacity is implied as battery_life is already in whole numbers. */
299     (void)snprintf(percentagebuf, sizeof(percentagebuf), "%.00d%%", apm_info.battery_life);
300
301     if (status == CS_DISCHARGING && low_threshold > 0) {
302         if (strcasecmp(threshold_type, "percentage") == 0 && apm_info.battery_life < low_threshold) {
303             START_COLOR("color_bad");
304             colorful_output = true;
305         } else if (strcasecmp(threshold_type, "time") == 0 && apm_info.minutes_left < (u_int)low_threshold) {
306             START_COLOR("color_bad");
307             colorful_output = true;
308         }
309     }
310
311     /* Can't give a meaningful value for remaining minutes if we're charging. */
312     if (status != CS_CHARGING) {
313         (void)snprintf(remainingbuf, sizeof(remainingbuf), "%d", apm_info.minutes_left);
314     } else {
315         (void)snprintf(remainingbuf, sizeof(remainingbuf), "%s", "(CHR)");
316     }
317
318     if (colorful_output)
319         END_COLOR;
320 #elif defined(__NetBSD__)
321     /*
322      * Using envsys(4) via sysmon(4).
323      */
324     int fd, rval, last_full_cap;
325     bool is_found = false;
326     char *sensor_desc;
327     bool is_full = false;
328
329     prop_dictionary_t dict;
330     prop_array_t array;
331     prop_object_iterator_t iter;
332     prop_object_iterator_t iter2;
333     prop_object_t obj, obj2, obj3, obj4, obj5;
334
335     asprintf(&sensor_desc, "acpibat%d", number);
336
337     fd = open("/dev/sysmon", O_RDONLY);
338     if (fd < 0) {
339         OUTPUT_FULL_TEXT("can't open /dev/sysmon");
340         return;
341     }
342
343     rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
344     if (rval == -1) {
345         close(fd);
346         return;
347     }
348
349     if (prop_dictionary_count(dict) == 0) {
350         prop_object_release(dict);
351         close(fd);
352         return;
353     }
354
355     iter = prop_dictionary_iterator(dict);
356     if (iter == NULL) {
357         prop_object_release(dict);
358         close(fd);
359     }
360
361     /* iterate over the dictionary returned by the kernel */
362     while ((obj = prop_object_iterator_next(iter)) != NULL) {
363         /* skip this dict if it's not what we're looking for */
364         if ((strlen(prop_dictionary_keysym_cstring_nocopy(obj)) == strlen(sensor_desc)) &&
365             (strncmp(sensor_desc,
366                      prop_dictionary_keysym_cstring_nocopy(obj),
367                      strlen(sensor_desc)) != 0))
368             continue;
369
370         is_found = true;
371
372         array = prop_dictionary_get_keysym(dict, obj);
373         if (prop_object_type(array) != PROP_TYPE_ARRAY) {
374             prop_object_iterator_release(iter);
375             prop_object_release(dict);
376             close(fd);
377             return;
378         }
379
380         iter2 = prop_array_iterator(array);
381         if (!iter2) {
382             prop_object_iterator_release(iter);
383             prop_object_release(dict);
384             close(fd);
385             return;
386         }
387
388         /* iterate over array of dicts specific to target battery */
389         while ((obj2 = prop_object_iterator_next(iter2)) != NULL) {
390             obj3 = prop_dictionary_get(obj2, "description");
391
392             if (obj3 &&
393                 strlen(prop_string_cstring_nocopy(obj3)) == 8 &&
394                 strncmp("charging",
395                         prop_string_cstring_nocopy(obj3),
396                         8) == 0) {
397                 obj3 = prop_dictionary_get(obj2, "cur-value");
398
399                 if (prop_number_integer_value(obj3))
400                     status = CS_CHARGING;
401                 else
402                     status = CS_DISCHARGING;
403
404                 continue;
405             }
406
407             if (obj3 &&
408                 strlen(prop_string_cstring_nocopy(obj3)) == 6 &&
409                 strncmp("charge",
410                         prop_string_cstring_nocopy(obj3),
411                         6) == 0) {
412                 obj3 = prop_dictionary_get(obj2, "cur-value");
413                 obj4 = prop_dictionary_get(obj2, "max-value");
414                 obj5 = prop_dictionary_get(obj2, "type");
415
416                 remaining = prop_number_integer_value(obj3);
417                 full_design = prop_number_integer_value(obj4);
418
419                 if (remaining == full_design)
420                     is_full = true;
421
422                 if (strncmp("Ampere hour",
423                             prop_string_cstring_nocopy(obj5),
424                             11) == 0)
425                     watt_as_unit = false;
426                 else
427                     watt_as_unit = true;
428
429                 continue;
430             }
431
432             if (obj3 &&
433                 strlen(prop_string_cstring_nocopy(obj3)) == 14 &&
434                 strncmp("discharge rate",
435                         prop_string_cstring_nocopy(obj3),
436                         14) == 0) {
437                 obj3 = prop_dictionary_get(obj2, "cur-value");
438                 present_rate = prop_number_integer_value(obj3);
439                 continue;
440             }
441
442             if (obj3 &&
443                 strlen(prop_string_cstring_nocopy(obj3)) == 13 &&
444                 strncmp("last full cap",
445                         prop_string_cstring_nocopy(obj3),
446                         13) == 0) {
447                 obj3 = prop_dictionary_get(obj2, "cur-value");
448                 last_full_cap = prop_number_integer_value(obj3);
449                 continue;
450             }
451
452             if (obj3 &&
453                 strlen(prop_string_cstring_nocopy(obj3)) == 7 &&
454                 strncmp("voltage",
455                         prop_string_cstring_nocopy(obj3),
456                         7) == 0) {
457                 obj3 = prop_dictionary_get(obj2, "cur-value");
458                 voltage = prop_number_integer_value(obj3);
459                 continue;
460             }
461         }
462         prop_object_iterator_release(iter2);
463     }
464
465     prop_object_iterator_release(iter);
466     prop_object_release(dict);
467     close(fd);
468
469     if (!is_found) {
470         OUTPUT_FULL_TEXT(format_down);
471         return;
472     }
473
474     if (last_full_capacity)
475         full_design = last_full_cap;
476
477     if (!watt_as_unit) {
478         present_rate = (((float)voltage / 1000.0) * ((float)present_rate / 1000.0));
479         remaining = (((float)voltage / 1000.0) * ((float)remaining / 1000.0));
480         full_design = (((float)voltage / 1000.0) * ((float)full_design / 1000.0));
481     }
482
483     float percentage_remaining =
484         (((float)remaining / (float)full_design) * 100);
485
486     if (integer_battery_capacity)
487         (void)snprintf(percentagebuf,
488                        sizeof(percentagebuf),
489                        "%d%%",
490                        (int)percentage_remaining);
491     else
492         (void)snprintf(percentagebuf,
493                        sizeof(percentagebuf),
494                        "%.02f%%",
495                        percentage_remaining);
496
497     /*
498      * Handle percentage low_threshold here, and time low_threshold when
499      * we have it.
500      */
501     if (status == CS_DISCHARGING && low_threshold > 0) {
502         if (strcasecmp(threshold_type, "percentage") == 0 && (((float)remaining / (float)full_design) * 100) < low_threshold) {
503             START_COLOR("color_bad");
504             colorful_output = true;
505         }
506     }
507
508     if (is_full)
509         (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(CS_FULL));
510     else
511         (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status));
512
513     /*
514      * The envsys(4) ACPI routines do not appear to provide a 'time
515      * remaining' figure, so we must deduce it.
516      */
517     float remaining_time;
518     int seconds, hours, minutes, seconds_remaining;
519
520     if (status == CS_CHARGING)
521         remaining_time = ((float)full_design - (float)remaining) / (float)present_rate;
522     else if (status == CS_DISCHARGING)
523         remaining_time = ((float)remaining / (float)present_rate);
524     else
525         remaining_time = 0;
526
527     seconds_remaining = (int)(remaining_time * 3600.0);
528
529     hours = seconds_remaining / 3600;
530     seconds = seconds_remaining - (hours * 3600);
531     minutes = seconds / 60;
532     seconds -= (minutes * 60);
533
534     if (status != CS_CHARGING) {
535         if (hide_seconds)
536             (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02d:%02d",
537                            max(hours, 0), max(minutes, 0));
538         else
539             (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02d:%02d:%02d",
540                            max(hours, 0), max(minutes, 0), max(seconds, 0));
541
542         if (low_threshold > 0) {
543             if (strcasecmp(threshold_type, "time") == 0 && ((float)seconds_remaining / 60.0) < (u_int)low_threshold) {
544                 START_COLOR("color_bad");
545                 colorful_output = true;
546             }
547         }
548     } else {
549         if (hide_seconds)
550             (void)snprintf(remainingbuf, sizeof(remainingbuf), "(%02d:%02d until full)",
551                            max(hours, 0), max(minutes, 0));
552         else
553             (void)snprintf(remainingbuf, sizeof(remainingbuf), "(%02d:%02d:%02d until full)",
554                            max(hours, 0), max(minutes, 0), max(seconds, 0));
555     }
556
557     empty_time = time(NULL);
558     empty_time += seconds_remaining;
559     empty_tm = localtime(&empty_time);
560
561     /* No need to show empty time if battery is charging */
562     if (status != CS_CHARGING) {
563         if (hide_seconds)
564             (void)snprintf(emptytimebuf, sizeof(emptytimebuf), "%02d:%02d",
565                            max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0));
566         else
567             (void)snprintf(emptytimebuf, sizeof(emptytimebuf), "%02d:%02d:%02d",
568                            max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0));
569     }
570
571     (void)snprintf(consumptionbuf, sizeof(consumptionbuf), "%1.2fW",
572                    ((float)present_rate / 1000.0 / 1000.0));
573 #endif
574
575 #define EAT_SPACE_FROM_OUTPUT_IF_EMPTY(_buf)              \
576     do {                                                  \
577         if (strlen(_buf) == 0) {                          \
578             if (outwalk > buffer && isspace(outwalk[-1])) \
579                 outwalk--;                                \
580             else if (isspace(*(walk + 1)))                \
581                 walk++;                                   \
582         }                                                 \
583     } while (0)
584
585     for (walk = format; *walk != '\0'; walk++) {
586         if (*walk != '%') {
587             *(outwalk++) = *walk;
588             continue;
589         }
590
591         if (BEGINS_WITH(walk + 1, "status")) {
592             outwalk += sprintf(outwalk, "%s", statusbuf);
593             walk += strlen("status");
594         } else if (BEGINS_WITH(walk + 1, "percentage")) {
595             outwalk += sprintf(outwalk, "%s", percentagebuf);
596             walk += strlen("percentage");
597         } else if (BEGINS_WITH(walk + 1, "remaining")) {
598             outwalk += sprintf(outwalk, "%s", remainingbuf);
599             walk += strlen("remaining");
600             EAT_SPACE_FROM_OUTPUT_IF_EMPTY(remainingbuf);
601         } else if (BEGINS_WITH(walk + 1, "emptytime")) {
602             outwalk += sprintf(outwalk, "%s", emptytimebuf);
603             walk += strlen("emptytime");
604             EAT_SPACE_FROM_OUTPUT_IF_EMPTY(emptytimebuf);
605         } else if (BEGINS_WITH(walk + 1, "consumption")) {
606             outwalk += sprintf(outwalk, "%s", consumptionbuf);
607             walk += strlen("consumption");
608             EAT_SPACE_FROM_OUTPUT_IF_EMPTY(consumptionbuf);
609         }
610     }
611
612     if (colorful_output)
613         END_COLOR;
614
615     OUTPUT_FULL_TEXT(buffer);
616 }