]> git.sur5r.net Git - i3/i3status/blob - src/print_battery_info.c
able to print percentage
[i3/i3status] / src / print_battery_info.c
1 // vim:ts=4:sw=4:expandtab
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <time.h>
7 #include <yajl/yajl_gen.h>
8 #include <yajl/yajl_version.h>
9
10 #include "i3status.h"
11
12 #if defined(LINUX)
13 #include <errno.h>
14 #include <glob.h>
15 #include <sys/types.h>
16 #endif
17
18 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
19 #include <dev/acpica/acpiio.h>
20 #include <sys/sysctl.h>
21 #include <sys/types.h>
22 #endif
23
24 #if defined(__DragonFly__)
25 #include <sys/fcntl.h>
26 #endif
27
28 #if defined(__OpenBSD__)
29 #include <machine/apmvar.h>
30 #include <sys/fcntl.h>
31 #include <sys/ioctl.h>
32 #include <sys/types.h>
33 #endif
34
35 #if defined(__NetBSD__)
36 #include <fcntl.h>
37 #include <prop/proplib.h>
38 #include <sys/envsys.h>
39 #endif
40
41 typedef enum {
42     CS_UNKNOWN,
43     CS_DISCHARGING,
44     CS_CHARGING,
45     CS_FULL,
46 } charging_status_t;
47
48 /* A description of the state of one or more batteries. */
49 struct battery_info {
50     /* measured properties */
51     int full_design;  /* in uAh */
52     int full_last;    /* in uAh */
53     int remaining;    /* in uAh */
54     int present_rate; /* in uA, always non-negative */
55
56     /* derived properties */
57     int seconds_remaining;
58     float percentage_remaining;
59     charging_status_t status;
60 };
61
62 #if defined(__DragonFly__)
63 #define ACPIDEV "/dev/acpi"
64 static int acpifd;
65
66 static bool acpi_init(void) {
67     if (acpifd == 0) {
68         acpifd = open(ACPIDEV, O_RDWR);
69         if (acpifd == -1)
70             acpifd = open(ACPIDEV, O_RDONLY);
71         if (acpifd == -1)
72             return false;
73     }
74     return true;
75 }
76 #endif
77
78 #if defined(LINUX) || defined(__NetBSD__)
79 /*
80  * Add batt_info data to acc.
81  */
82 static void add_battery_info(struct battery_info *acc, const struct battery_info *batt_info) {
83     if (acc->remaining < 0) {
84         /* initialize accumulator so we can add to it */
85         acc->full_design = 0;
86         acc->full_last = 0;
87         acc->remaining = 0;
88         acc->present_rate = 0;
89     }
90
91     acc->full_design += batt_info->full_design;
92     acc->full_last += batt_info->full_last;
93     acc->remaining += batt_info->remaining;
94
95     /* make present_rate negative for discharging and positive for charging */
96     int present_rate = (acc->status == CS_DISCHARGING ? -1 : 1) * acc->present_rate;
97     present_rate += (batt_info->status == CS_DISCHARGING ? -1 : 1) * batt_info->present_rate;
98
99     /* merge status */
100     switch (acc->status) {
101         case CS_UNKNOWN:
102             acc->status = batt_info->status;
103             break;
104
105         case CS_DISCHARGING:
106             if (present_rate > 0)
107                 acc->status = CS_CHARGING;
108             /* else if batt_info is DISCHARGING: no conflict
109              * else if batt_info is CHARGING: present_rate should indicate that
110              * else if batt_info is FULL: but something else is discharging */
111             break;
112
113         case CS_CHARGING:
114             if (present_rate < 0)
115                 acc->status = CS_DISCHARGING;
116             /* else if batt_info is DISCHARGING: present_rate should indicate that
117              * else if batt_info is CHARGING: no conflict
118              * else if batt_info is FULL: but something else is charging */
119             break;
120
121         case CS_FULL:
122             if (batt_info->status != CS_UNKNOWN)
123                 acc->status = batt_info->status;
124             /* else: retain FULL, since it is more specific than UNKNOWN */
125             break;
126     }
127
128     acc->present_rate = abs(present_rate);
129 }
130 #endif
131
132 static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen, char *buffer, int number, const char *path, const char *format_down) {
133     char *outwalk = buffer;
134
135 #if defined(LINUX)
136     char buf[1024];
137     const char *walk, *last;
138     bool watt_as_unit = false;
139     int voltage = -1;
140     char batpath[512];
141     sprintf(batpath, path, number);
142     INSTANCE(batpath);
143
144     if (!slurp(batpath, buf, sizeof(buf))) {
145         OUTPUT_FULL_TEXT(format_down);
146         return false;
147     }
148
149     for (walk = buf, last = buf; (walk - buf) < 1024; walk++) {
150         if (*walk == '\n') {
151             last = walk + 1;
152             continue;
153         }
154
155         if (*walk != '=')
156             continue;
157
158         if (BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_NOW=")) {
159             watt_as_unit = true;
160             batt_info->remaining = atoi(walk + 1);
161             batt_info->percentage_remaining = -1;
162         } else if (BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_NOW=")) {
163             watt_as_unit = false;
164             batt_info->remaining = atoi(walk + 1);
165             batt_info->percentage_remaining = -1;
166         } else if (BEGINS_WITH(last, "POWER_SUPPLY_CAPACITY=") && batt_info->remaining == -1) {
167             batt_info->percentage_remaining = atoi(walk + 1);
168         } else if (BEGINS_WITH(last, "POWER_SUPPLY_CURRENT_NOW="))
169             batt_info->present_rate = abs(atoi(walk + 1));
170         else if (BEGINS_WITH(last, "POWER_SUPPLY_VOLTAGE_NOW="))
171             voltage = abs(atoi(walk + 1));
172         /* on some systems POWER_SUPPLY_POWER_NOW does not exist, but actually
173          * it is the same as POWER_SUPPLY_CURRENT_NOW but with μWh as
174          * unit instead of μAh. We will calculate it as we need it
175          * later. */
176         else if (BEGINS_WITH(last, "POWER_SUPPLY_POWER_NOW="))
177             batt_info->present_rate = abs(atoi(walk + 1));
178         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Charging"))
179             batt_info->status = CS_CHARGING;
180         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Full"))
181             batt_info->status = CS_FULL;
182         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Discharging"))
183             batt_info->status = CS_DISCHARGING;
184         else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS="))
185             batt_info->status = CS_UNKNOWN;
186         else if (BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL_DESIGN=") ||
187                  BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL_DESIGN="))
188             batt_info->full_design = atoi(walk + 1);
189         else if (BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL=") ||
190                  BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL="))
191             batt_info->full_last = atoi(walk + 1);
192     }
193
194     /* the difference between POWER_SUPPLY_ENERGY_NOW and
195      * POWER_SUPPLY_CHARGE_NOW is the unit of measurement. The energy is
196      * given in mWh, the charge in mAh. So calculate every value given in
197      * ampere to watt */
198     if (!watt_as_unit && voltage >= 0) {
199         if (batt_info->present_rate > 0) {
200             batt_info->present_rate = (((float)voltage / 1000.0) * ((float)batt_info->present_rate / 1000.0));
201         }
202         if (batt_info->remaining > 0) {
203             batt_info->remaining = (((float)voltage / 1000.0) * ((float)batt_info->remaining / 1000.0));
204         }
205         if (batt_info->full_design > 0) {
206             batt_info->full_design = (((float)voltage / 1000.0) * ((float)batt_info->full_design / 1000.0));
207         }
208         if (batt_info->full_last > 0) {
209             batt_info->full_last = (((float)voltage / 1000.0) * ((float)batt_info->full_last / 1000.0));
210         }
211     }
212 #elif defined(__DragonFly__)
213     union acpi_battery_ioctl_arg battio;
214     if (acpi_init()) {
215         battio.unit = number;
216         ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio);
217         batt_info->full_design = battio.bif.dcap;
218         batt_info->full_last = battio.bif.lfcap;
219         battio.unit = number;
220         ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio);
221         batt_info->percentage_remaining = battio.battinfo.cap;
222         batt_info->present_rate = battio.battinfo.rate;
223         batt_info->seconds_remaining = battio.battinfo.min * 60;
224         switch (battio.battinfo.state) {
225             case 0:
226                 batt_info->status = CS_FULL;
227                 break;
228             case ACPI_BATT_STAT_CHARGING:
229                 batt_info->status = CS_CHARGING;
230                 break;
231             case ACPI_BATT_STAT_DISCHARG:
232                 batt_info->status = CS_DISCHARGING;
233                 break;
234             default:
235                 batt_info->status = CS_UNKNOWN;
236         }
237         OUTPUT_FULL_TEXT(format_down);
238     }
239 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
240     int state;
241     int sysctl_rslt;
242     size_t sysctl_size = sizeof(sysctl_rslt);
243
244     if (sysctlbyname(BATT_LIFE, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
245         OUTPUT_FULL_TEXT(format_down);
246         return false;
247     }
248
249     batt_info->percentage_remaining = sysctl_rslt;
250     if (sysctlbyname(BATT_TIME, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
251         OUTPUT_FULL_TEXT(format_down);
252         return false;
253     }
254
255     batt_info->seconds_remaining = sysctl_rslt * 60;
256     if (sysctlbyname(BATT_STATE, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) {
257         OUTPUT_FULL_TEXT(format_down);
258         return false;
259     }
260
261     state = sysctl_rslt;
262     if (state == 0 && batt_info->percentage_remaining == 100)
263         batt_info->status = CS_FULL;
264     else if ((state & ACPI_BATT_STAT_CHARGING) && batt_info->percentage_remaining < 100)
265         batt_info->status = CS_CHARGING;
266     else
267         batt_info->status = CS_DISCHARGING;
268 #elif defined(__OpenBSD__)
269     /*
270          * We're using apm(4) here, which is the interface to acpi(4) on amd64/i386 and
271          * the generic interface on macppc/sparc64/zaurus, instead of using sysctl(3) and
272          * probing acpi(4) devices.
273          */
274     struct apm_power_info apm_info;
275     int apm_fd;
276
277     apm_fd = open("/dev/apm", O_RDONLY);
278     if (apm_fd < 0) {
279         OUTPUT_FULL_TEXT("can't open /dev/apm");
280         return false;
281     }
282     if (ioctl(apm_fd, APM_IOC_GETPOWER, &apm_info) < 0)
283         OUTPUT_FULL_TEXT("can't read power info");
284
285     close(apm_fd);
286
287     /* Don't bother to go further if there's no battery present. */
288     if ((apm_info.battery_state == APM_BATTERY_ABSENT) ||
289         (apm_info.battery_state == APM_BATT_UNKNOWN)) {
290         OUTPUT_FULL_TEXT(format_down);
291         return false;
292     }
293
294     switch (apm_info.ac_state) {
295         case APM_AC_OFF:
296             batt_info->status = CS_DISCHARGING;
297             break;
298         case APM_AC_ON:
299             batt_info->status = CS_CHARGING;
300             break;
301         default:
302             /* If we don't know what's going on, just assume we're discharging. */
303             batt_info->status = CS_DISCHARGING;
304             break;
305     }
306
307     batt_info->percentage_remaining = apm_info.battery_life;
308
309     /* Can't give a meaningful value for remaining minutes if we're charging. */
310     if (batt_info->status != CS_CHARGING) {
311         batt_info->seconds_remaining = apm_info.minutes_left * 60;
312     }
313 #elif defined(__NetBSD__)
314     /*
315      * Using envsys(4) via sysmon(4).
316      */
317     int fd, rval;
318     bool is_found = false;
319     char sensor_desc[16];
320
321     prop_dictionary_t dict;
322     prop_array_t array;
323     prop_object_iterator_t iter;
324     prop_object_iterator_t iter2;
325     prop_object_t obj, obj2, obj3, obj4, obj5;
326
327     if (number >= 0)
328         (void)snprintf(sensor_desc, sizeof(sensor_desc), "acpibat%d", number);
329
330     fd = open("/dev/sysmon", O_RDONLY);
331     if (fd < 0) {
332         OUTPUT_FULL_TEXT("can't open /dev/sysmon");
333         return false;
334     }
335
336     rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
337     if (rval == -1) {
338         close(fd);
339         return false;
340     }
341
342     if (prop_dictionary_count(dict) == 0) {
343         prop_object_release(dict);
344         close(fd);
345         return false;
346     }
347
348     iter = prop_dictionary_iterator(dict);
349     if (iter == NULL) {
350         prop_object_release(dict);
351         close(fd);
352     }
353
354     /* iterate over the dictionary returned by the kernel */
355     while ((obj = prop_object_iterator_next(iter)) != NULL) {
356         /* skip this dict if it's not what we're looking for */
357         if (number < 0) {
358             /* we want all batteries */
359             if (!BEGINS_WITH(prop_dictionary_keysym_cstring_nocopy(obj),
360                              "acpibat"))
361                 continue;
362         } else {
363             /* we want a specific battery */
364             if (strcmp(sensor_desc,
365                        prop_dictionary_keysym_cstring_nocopy(obj)) != 0)
366                 continue;
367         }
368
369         is_found = true;
370
371         array = prop_dictionary_get_keysym(dict, obj);
372         if (prop_object_type(array) != PROP_TYPE_ARRAY) {
373             prop_object_iterator_release(iter);
374             prop_object_release(dict);
375             close(fd);
376             return false;
377         }
378
379         iter2 = prop_array_iterator(array);
380         if (!iter2) {
381             prop_object_iterator_release(iter);
382             prop_object_release(dict);
383             close(fd);
384             return false;
385         }
386
387         struct battery_info batt_buf = {
388             .full_design = 0,
389             .full_last = 0,
390             .remaining = 0,
391             .present_rate = 0,
392             .status = CS_UNKNOWN,
393         };
394         int voltage = -1;
395         bool watt_as_unit = false;
396
397         /* iterate over array of dicts specific to target battery */
398         while ((obj2 = prop_object_iterator_next(iter2)) != NULL) {
399             obj3 = prop_dictionary_get(obj2, "description");
400
401             if (obj3 == NULL)
402                 continue;
403
404             if (strcmp("charging", prop_string_cstring_nocopy(obj3)) == 0) {
405                 obj3 = prop_dictionary_get(obj2, "cur-value");
406
407                 if (prop_number_integer_value(obj3))
408                     batt_buf.status = CS_CHARGING;
409                 else
410                     batt_buf.status = CS_DISCHARGING;
411             } else if (strcmp("charge", prop_string_cstring_nocopy(obj3)) == 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                 batt_buf.remaining = prop_number_integer_value(obj3);
417                 batt_buf.full_design = prop_number_integer_value(obj4);
418
419                 if (strcmp("Ampere hour", prop_string_cstring_nocopy(obj5)) == 0)
420                     watt_as_unit = false;
421                 else
422                     watt_as_unit = true;
423             } else if (strcmp("discharge rate", prop_string_cstring_nocopy(obj3)) == 0) {
424                 obj3 = prop_dictionary_get(obj2, "cur-value");
425                 batt_buf.present_rate = prop_number_integer_value(obj3);
426             } else if (strcmp("charge rate", prop_string_cstring_nocopy(obj3)) == 0) {
427                 obj3 = prop_dictionary_get(obj2, "cur-value");
428                 batt_info->present_rate = prop_number_integer_value(obj3);
429             } else if (strcmp("last full cap", prop_string_cstring_nocopy(obj3)) == 0) {
430                 obj3 = prop_dictionary_get(obj2, "cur-value");
431                 batt_buf.full_last = prop_number_integer_value(obj3);
432             } else if (strcmp("voltage", prop_string_cstring_nocopy(obj3)) == 0) {
433                 obj3 = prop_dictionary_get(obj2, "cur-value");
434                 voltage = prop_number_integer_value(obj3);
435             }
436         }
437         prop_object_iterator_release(iter2);
438
439         if (!watt_as_unit && voltage != -1) {
440             batt_buf.present_rate = (((float)voltage / 1000.0) * ((float)batt_buf.present_rate / 1000.0));
441             batt_buf.remaining = (((float)voltage / 1000.0) * ((float)batt_buf.remaining / 1000.0));
442             batt_buf.full_design = (((float)voltage / 1000.0) * ((float)batt_buf.full_design / 1000.0));
443             batt_buf.full_last = (((float)voltage / 1000.0) * ((float)batt_buf.full_last / 1000.0));
444         }
445
446         if (batt_buf.remaining == batt_buf.full_design)
447             batt_buf.status = CS_FULL;
448
449         add_battery_info(batt_info, &batt_buf);
450     }
451
452     prop_object_iterator_release(iter);
453     prop_object_release(dict);
454     close(fd);
455
456     if (!is_found) {
457         OUTPUT_FULL_TEXT(format_down);
458         return false;
459     }
460
461     batt_info->present_rate = abs(batt_info->present_rate);
462 #endif
463
464     return true;
465 }
466
467 /*
468  * Populate batt_info with aggregate information about all batteries.
469  * Returns false on error, and an error message will have been written.
470  */
471 static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_gen, char *buffer, const char *path, const char *format_down) {
472 #if defined(LINUX)
473     char *outwalk = buffer;
474     bool is_found = false;
475
476     char *placeholder;
477     char *globpath = sstrdup(path);
478     if ((placeholder = strstr(path, "%d")) != NULL) {
479         char *globplaceholder = globpath + (placeholder - path);
480         *globplaceholder = '*';
481         strcpy(globplaceholder + 1, placeholder + 2);
482     }
483
484     if (!strcmp(globpath, path)) {
485         OUTPUT_FULL_TEXT("no '%d' in battery path");
486         return false;
487     }
488
489     glob_t globbuf;
490     if (glob(globpath, 0, NULL, &globbuf) == 0) {
491         for (size_t i = 0; i < globbuf.gl_pathc; i++) {
492             /* Probe to see if there is such a battery. */
493             struct battery_info batt_buf = {
494                 .full_design = 0,
495                 .full_last = 0,
496                 .remaining = 0,
497                 .present_rate = 0,
498                 .status = CS_UNKNOWN,
499             };
500             if (!slurp_battery_info(&batt_buf, json_gen, buffer, i, globbuf.gl_pathv[i], format_down)) {
501                 globfree(&globbuf);
502                 free(globpath);
503                 return false;
504             }
505
506             is_found = true;
507             add_battery_info(batt_info, &batt_buf);
508         }
509         globfree(&globbuf);
510     }
511     free(globpath);
512
513     if (!is_found) {
514         OUTPUT_FULL_TEXT(format_down);
515         return false;
516     }
517
518     batt_info->present_rate = abs(batt_info->present_rate);
519 #else
520     /* FreeBSD and OpenBSD only report aggregates. NetBSD always
521      * iterates through all batteries, so it's more efficient to
522      * aggregate in slurp_battery_info. */
523     return slurp_battery_info(batt_info, json_gen, buffer, -1, path, format_down);
524 #endif
525
526     return true;
527 }
528
529 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_unk, const char *status_full, int low_threshold, char *threshold_type, bool last_full_capacity, bool integer_battery_capacity, bool hide_seconds) {
530     const char *walk;
531     char *outwalk = buffer;
532     struct battery_info batt_info = {
533         .full_design = -1,
534         .full_last = -1,
535         .remaining = -1,
536         .present_rate = -1,
537         .seconds_remaining = -1,
538         .percentage_remaining = -1,
539         .status = CS_UNKNOWN,
540     };
541     bool colorful_output = false;
542
543 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__OpenBSD__)
544     /* These OSes report battery stats in whole percent. */
545     integer_battery_capacity = true;
546 #endif
547 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__OpenBSD__)
548     /* These OSes report battery time in minutes. */
549     hide_seconds = true;
550 #endif
551
552     if (number < 0) {
553         if (!slurp_all_batteries(&batt_info, json_gen, buffer, path, format_down))
554             return;
555     } else {
556         if (!slurp_battery_info(&batt_info, json_gen, buffer, number, path, format_down))
557             return;
558     }
559
560     // *Choose* a measure of the 'full' battery. It is whichever is better of
561     // the battery's (hardware-given) design capacity (batt_info.full_design)
562     // and the battery's last known good charge (batt_info.full_last).
563     // We prefer the design capacity, but use the last capacity if we don't have it,
564     // or if we are asked to (last_full_capacity == true); but similarly we use
565     // the design capacity if we don't have the last capacity.
566     // If we don't have either then both full_design and full_last <= 0,
567     // which implies full <= 0, which bails out on the following line.
568     int full = batt_info.full_design;
569     if (full <= 0 || (last_full_capacity && batt_info.full_last > 0)) {
570         full = batt_info.full_last;
571     }
572     if (full <= 0 && batt_info.remaining < 0 && batt_info.percentage_remaining < 0) {
573         /* We have no physical measurements and no estimates. Nothing
574          * much we can report, then. */
575         OUTPUT_FULL_TEXT(format_down);
576         return;
577     }
578
579     if (batt_info.percentage_remaining < 0) {
580         batt_info.percentage_remaining = (((float)batt_info.remaining / (float)full) * 100);
581         /* Some batteries report POWER_SUPPLY_CHARGE_NOW=<full_design> when fully
582          * charged, even though that’s plainly wrong. For people who chose to see
583          * the percentage calculated based on the last full capacity, we clamp the
584          * value to 100%, as that makes more sense.
585          * See http://bugs.debian.org/785398 */
586         if (last_full_capacity && batt_info.percentage_remaining > 100) {
587             batt_info.percentage_remaining = 100;
588         }
589     }
590
591     if (batt_info.seconds_remaining < 0 && batt_info.present_rate > 0 && batt_info.status != CS_FULL) {
592         if (batt_info.status == CS_CHARGING)
593             batt_info.seconds_remaining = 3600.0 * (full - batt_info.remaining) / batt_info.present_rate;
594         else if (batt_info.status == CS_DISCHARGING)
595             batt_info.seconds_remaining = 3600.0 * batt_info.remaining / batt_info.present_rate;
596         else
597             batt_info.seconds_remaining = 0;
598     }
599
600     if (batt_info.status == CS_DISCHARGING && low_threshold > 0) {
601         if (batt_info.percentage_remaining >= 0 && strcasecmp(threshold_type, "percentage") == 0 && batt_info.percentage_remaining < low_threshold) {
602             START_COLOR("color_bad");
603             colorful_output = true;
604         } else if (batt_info.seconds_remaining >= 0 && strcasecmp(threshold_type, "time") == 0 && batt_info.seconds_remaining < 60 * low_threshold) {
605             START_COLOR("color_bad");
606             colorful_output = true;
607         }
608     }
609
610 #define EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT()                   \
611     do {                                                       \
612         if (outwalk == prevoutwalk) {                          \
613             if (outwalk > buffer && isspace((int)outwalk[-1])) \
614                 outwalk--;                                     \
615             else if (isspace((int)*(walk + 1)))                \
616                 walk++;                                        \
617         }                                                      \
618     } while (0)
619
620     for (walk = format; *walk != '\0'; walk++) {
621         char *prevoutwalk = outwalk;
622
623         if (*walk != '%') {
624             *(outwalk++) = *walk;
625
626         } else if (BEGINS_WITH(walk + 1, "status")) {
627             const char *statusstr;
628             switch (batt_info.status) {
629                 case CS_CHARGING:
630                     statusstr = status_chr;
631                     break;
632                 case CS_DISCHARGING:
633                     statusstr = status_bat;
634                     break;
635                 case CS_FULL:
636                     statusstr = status_full;
637                     break;
638                 default:
639                     statusstr = status_unk;
640             }
641
642             outwalk += sprintf(outwalk, "%s", statusstr);
643             walk += strlen("status");
644
645         } else if (BEGINS_WITH(walk + 1, "percentage")) {
646             if (integer_battery_capacity) {
647                 outwalk += sprintf(outwalk, "%.00f%s", batt_info.percentage_remaining, pct_mark);
648             } else {
649                 outwalk += sprintf(outwalk, "%.02f%s", batt_info.percentage_remaining, pct_mark);
650             }
651             walk += strlen("percentage");
652
653         } else if (BEGINS_WITH(walk + 1, "remaining")) {
654             if (batt_info.seconds_remaining >= 0) {
655                 int seconds, hours, minutes;
656
657                 hours = batt_info.seconds_remaining / 3600;
658                 seconds = batt_info.seconds_remaining - (hours * 3600);
659                 minutes = seconds / 60;
660                 seconds -= (minutes * 60);
661
662                 if (hide_seconds)
663                     outwalk += sprintf(outwalk, "%02d:%02d",
664                                        max(hours, 0), max(minutes, 0));
665                 else
666                     outwalk += sprintf(outwalk, "%02d:%02d:%02d",
667                                        max(hours, 0), max(minutes, 0), max(seconds, 0));
668             }
669             walk += strlen("remaining");
670             EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
671
672         } else if (BEGINS_WITH(walk + 1, "emptytime")) {
673             if (batt_info.seconds_remaining >= 0) {
674                 time_t empty_time = time(NULL) + batt_info.seconds_remaining;
675                 set_timezone(NULL); /* Use local time. */
676                 struct tm *empty_tm = localtime(&empty_time);
677
678                 if (hide_seconds)
679                     outwalk += sprintf(outwalk, "%02d:%02d",
680                                        max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0));
681                 else
682                     outwalk += sprintf(outwalk, "%02d:%02d:%02d",
683                                        max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0));
684             }
685             walk += strlen("emptytime");
686             EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
687
688         } else if (BEGINS_WITH(walk + 1, "consumption")) {
689             if (batt_info.present_rate >= 0)
690                 outwalk += sprintf(outwalk, "%1.2fW", batt_info.present_rate / 1e6);
691
692             walk += strlen("consumption");
693             EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
694
695         } else {
696             *(outwalk++) = '%';
697         }
698     }
699
700     if (colorful_output)
701         END_COLOR;
702
703     OUTPUT_FULL_TEXT(buffer);
704 }