diff options
| -rw-r--r-- | i3status.c | 2 | ||||
| -rw-r--r-- | i3status.conf | 4 | ||||
| -rw-r--r-- | include/i3status.h | 5 | ||||
| -rw-r--r-- | man/i3status.man | 12 | ||||
| -rw-r--r-- | src/print_battery_info.c | 215 | 
5 files changed, 197 insertions, 41 deletions
| @@ -664,7 +664,7 @@ int main(int argc, char *argv[]) {              CASE_SEC_TITLE("battery") {                  SEC_OPEN_MAP("battery"); -                print_battery_info(json_gen, buffer, atoi(title), cfg_getstr(sec, "path"), cfg_getstr(sec, "format"), cfg_getstr(sec, "format_down"), cfg_getstr(sec, "status_chr"), cfg_getstr(sec, "status_bat"), cfg_getstr(sec, "status_unk"), cfg_getstr(sec, "status_full"), cfg_getint(sec, "low_threshold"), cfg_getstr(sec, "threshold_type"), cfg_getbool(sec, "last_full_capacity"), cfg_getbool(sec, "integer_battery_capacity"), cfg_getbool(sec, "hide_seconds")); +                print_battery_info(json_gen, buffer, (strcasecmp(title, "all") == 0 ? -1 : atoi(title)), cfg_getstr(sec, "path"), cfg_getstr(sec, "format"), cfg_getstr(sec, "format_down"), cfg_getstr(sec, "status_chr"), cfg_getstr(sec, "status_bat"), cfg_getstr(sec, "status_unk"), cfg_getstr(sec, "status_full"), cfg_getint(sec, "low_threshold"), cfg_getstr(sec, "threshold_type"), cfg_getbool(sec, "last_full_capacity"), cfg_getbool(sec, "integer_battery_capacity"), cfg_getbool(sec, "hide_seconds"));                  SEC_CLOSE_MAP;              } diff --git a/i3status.conf b/i3status.conf index 2d71a54..7f37964 100644 --- a/i3status.conf +++ b/i3status.conf @@ -15,7 +15,7 @@ order += "ipv6"  order += "disk /"  order += "wireless _first_"  order += "ethernet _first_" -order += "battery 0" +order += "battery all"  order += "load"  order += "tztime local" @@ -30,7 +30,7 @@ ethernet _first_ {          format_down = "E: down"  } -battery 0 { +battery all {          format = "%status %percentage %remaining"  } diff --git a/include/i3status.h b/include/i3status.h index 4d2d0f1..3911e04 100644 --- a/include/i3status.h +++ b/include/i3status.h @@ -168,11 +168,6 @@ char *pct_mark;          }                                                                                      \      } while (0) -typedef enum { CS_DISCHARGING, -               CS_CHARGING, -               CS_UNKNOWN, -               CS_FULL } charging_status_t; -  /*   * The "min_width" module option may either be defined as a string or a number.   */ diff --git a/man/i3status.man b/man/i3status.man index d0ca2a0..cdde2aa 100644 --- a/man/i3status.man +++ b/man/i3status.man @@ -336,6 +336,10 @@ colored red. The low_threshold type can be of threshold_type "time" or  "percentage". So, if you configure low_threshold to 10 and threshold_type to  "time", and your battery lasts another 9 minutes, it will be colored red. +To show an aggregate of all batteries in the system, use "all" as the number. In +this case (for Linux), the /sys path must contain the "%d" sequence. Otherwise, +the number indicates the battery index as reported in /sys. +  Optionally custom strings including any UTF-8 symbols can be used for different  battery states. This makes it possible to display individual symbols  for each state (charging, discharging, unknown, full) @@ -343,7 +347,9 @@ Of course it will also work with special iconic fonts, such as FontAwesome.  If any of these special status strings are omitted, the default (CHR, BAT, UNK,  FULL) is used. -*Example order*: +battery 0+ +*Example order (for the first battery)*: +battery 0+ + +*Example order (aggregate of all batteries)*: +battery all+  *Example format*: +%status %remaining (%emptytime %consumption)+ @@ -361,7 +367,9 @@ FULL) is used.  *Example threshold_type*: +time+ -*Example path*: +/sys/class/power_supply/CMB1/uevent+ +*Example path (%d replaced by title number)*: +/sys/class/power_supply/CMB%d/uevent+ + +*Example path (ignoring the number)*: +/sys/class/power_supply/CMB1/uevent+  === CPU-Temperature diff --git a/src/print_battery_info.c b/src/print_battery_info.c index 27473a2..52d5031 100644 --- a/src/print_battery_info.c +++ b/src/print_battery_info.c @@ -9,6 +9,12 @@  #include "i3status.h" +#if defined(LINUX) +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#endif +  #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)  #include <sys/types.h>  #include <sys/sysctl.h> @@ -28,17 +34,81 @@  #include <sys/envsys.h>  #endif +typedef enum { +    CS_UNKNOWN, +    CS_DISCHARGING, +    CS_CHARGING, +    CS_FULL, +} charging_status_t; + +/* A description of the state of one or more batteries. */  struct battery_info { -    int full_design; -    int full_last; -    int remaining; +    /* measured properties */ +    int full_design;  /* in uAh */ +    int full_last;    /* in uAh */ +    int remaining;    /* in uAh */ +    int present_rate; /* in uA, always non-negative */ -    int present_rate; +    /* derived properties */      int seconds_remaining;      float percentage_remaining;      charging_status_t status;  }; +#if defined(LINUX) || defined(__NetBSD__) +/* + * Add batt_info data to acc. + */ +static void add_battery_info(struct battery_info *acc, const struct battery_info *batt_info) { +    if (acc->remaining < 0) { +        /* initialize accumulator so we can add to it */ +        acc->full_design = 0; +        acc->full_last = 0; +        acc->remaining = 0; +        acc->present_rate = 0; +    } + +    acc->full_design += batt_info->full_design; +    acc->full_last += batt_info->full_last; +    acc->remaining += batt_info->remaining; + +    /* make present_rate negative for discharging and positive for charging */ +    int present_rate = (acc->status == CS_DISCHARGING ? -1 : 1) * acc->present_rate; +    present_rate += (batt_info->status == CS_DISCHARGING ? -1 : 1) * batt_info->present_rate; + +    /* merge status */ +    switch (acc->status) { +        case CS_UNKNOWN: +            acc->status = batt_info->status; +            break; + +        case CS_DISCHARGING: +            if (present_rate > 0) +                acc->status = CS_CHARGING; +            /* else if batt_info is DISCHARGING: no conflict +             * else if batt_info is CHARGING: present_rate should indicate that +             * else if batt_info is FULL: but something else is discharging */ +            break; + +        case CS_CHARGING: +            if (present_rate < 0) +                acc->status = CS_DISCHARGING; +            /* else if batt_info is DISCHARGING: present_rate should indicate that +             * else if batt_info is CHARGING: no conflict +             * else if batt_info is FULL: but something else is charging */ +            break; + +        case CS_FULL: +            if (batt_info->status != CS_UNKNOWN) +                acc->status = batt_info->status; +            /* else: retain FULL, since it is more specific than UNKNOWN */ +            break; +    } + +    acc->present_rate = abs(present_rate); +} +#endif +  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) {      char *outwalk = buffer; @@ -185,12 +255,9 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen      /*       * Using envsys(4) via sysmon(4).       */ -    bool watt_as_unit = false; -    int voltage = -1;      int fd, rval;      bool is_found = false; -    char *sensor_desc; -    bool is_full = false; +    char sensor_desc[16];      prop_dictionary_t dict;      prop_array_t array; @@ -198,7 +265,8 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen      prop_object_iterator_t iter2;      prop_object_t obj, obj2, obj3, obj4, obj5; -    asprintf(&sensor_desc, "acpibat%d", number); +    if (number >= 0) +        (void)snprintf(sensor_desc, sizeof(sensor_desc), "acpibat%d", number);      fd = open("/dev/sysmon", O_RDONLY);      if (fd < 0) { @@ -227,9 +295,17 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen      /* iterate over the dictionary returned by the kernel */      while ((obj = prop_object_iterator_next(iter)) != NULL) {          /* skip this dict if it's not what we're looking for */ -        if (strcmp(sensor_desc, -                   prop_dictionary_keysym_cstring_nocopy(obj)) != 0) -            continue; +        if (number < 0) { +            /* we want all batteries */ +            if (!BEGINS_WITH(prop_dictionary_keysym_cstring_nocopy(obj), +                             "acpibat")) +                continue; +        } else { +            /* we want a specific battery */ +            if (strcmp(sensor_desc, +                       prop_dictionary_keysym_cstring_nocopy(obj)) != 0) +                continue; +        }          is_found = true; @@ -249,6 +325,16 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen              return false;          } +        struct battery_info batt_buf = { +            .full_design = 0, +            .full_last = 0, +            .remaining = 0, +            .present_rate = 0, +            .status = CS_UNKNOWN, +        }; +        int voltage = -1; +        bool watt_as_unit = false; +          /* iterate over array of dicts specific to target battery */          while ((obj2 = prop_object_iterator_next(iter2)) != NULL) {              obj3 = prop_dictionary_get(obj2, "description"); @@ -260,19 +346,16 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen                  obj3 = prop_dictionary_get(obj2, "cur-value");                  if (prop_number_integer_value(obj3)) -                    batt_info->status = CS_CHARGING; +                    batt_buf.status = CS_CHARGING;                  else -                    batt_info->status = CS_DISCHARGING; +                    batt_buf.status = CS_DISCHARGING;              } else if (strcmp("charge", prop_string_cstring_nocopy(obj3)) == 0) {                  obj3 = prop_dictionary_get(obj2, "cur-value");                  obj4 = prop_dictionary_get(obj2, "max-value");                  obj5 = prop_dictionary_get(obj2, "type"); -                batt_info->remaining = prop_number_integer_value(obj3); -                batt_info->full_design = prop_number_integer_value(obj4); - -                if (batt_info->remaining == batt_info->full_design) -                    is_full = true; +                batt_buf.remaining = prop_number_integer_value(obj3); +                batt_buf.full_design = prop_number_integer_value(obj4);                  if (strcmp("Ampere hour", prop_string_cstring_nocopy(obj5)) == 0)                      watt_as_unit = false; @@ -280,19 +363,31 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen                      watt_as_unit = true;              } else if (strcmp("discharge rate", prop_string_cstring_nocopy(obj3)) == 0) {                  obj3 = prop_dictionary_get(obj2, "cur-value"); -                batt_info->present_rate = prop_number_integer_value(obj3); +                batt_buf.present_rate = prop_number_integer_value(obj3);              } else if (strcmp("charge rate", prop_string_cstring_nocopy(obj3)) == 0) {                  obj3 = prop_dictionary_get(obj2, "cur-value");                  batt_info->present_rate = prop_number_integer_value(obj3);              } else if (strcmp("last full cap", prop_string_cstring_nocopy(obj3)) == 0) {                  obj3 = prop_dictionary_get(obj2, "cur-value"); -                batt_info->full_last = prop_number_integer_value(obj3); +                batt_buf.full_last = prop_number_integer_value(obj3);              } else if (strcmp("voltage", prop_string_cstring_nocopy(obj3)) == 0) {                  obj3 = prop_dictionary_get(obj2, "cur-value");                  voltage = prop_number_integer_value(obj3);              }          }          prop_object_iterator_release(iter2); + +        if (!watt_as_unit && voltage != -1) { +            batt_buf.present_rate = (((float)voltage / 1000.0) * ((float)batt_buf.present_rate / 1000.0)); +            batt_buf.remaining = (((float)voltage / 1000.0) * ((float)batt_buf.remaining / 1000.0)); +            batt_buf.full_design = (((float)voltage / 1000.0) * ((float)batt_buf.full_design / 1000.0)); +            batt_buf.full_last = (((float)voltage / 1000.0) * ((float)batt_buf.full_last / 1000.0)); +        } + +        if (batt_buf.remaining == batt_buf.full_design) +            batt_buf.status = CS_FULL; + +        add_battery_info(batt_info, &batt_buf);      }      prop_object_iterator_release(iter); @@ -304,15 +399,67 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen          return false;      } -    if (!watt_as_unit && voltage != -1) { -        batt_info->present_rate = (((float)voltage / 1000.0) * ((float)batt_info->present_rate / 1000.0)); -        batt_info->remaining = (((float)voltage / 1000.0) * ((float)batt_info->remaining / 1000.0)); -        batt_info->full_design = (((float)voltage / 1000.0) * ((float)batt_info->full_design / 1000.0)); -        batt_info->full_last = (((float)voltage / 1000.0) * ((float)batt_info->full_last / 1000.0)); +    batt_info->present_rate = abs(batt_info->present_rate); +#endif + +    return true; +} + +/* + * Populate batt_info with aggregate information about all batteries. + * Returns false on error, and an error message will have been written. + */ +static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_gen, char *buffer, const char *path, const char *format_down) { +#if defined(LINUX) +    char *outwalk = buffer; +    bool is_found = false; + +    /* 1,000 batteries should be enough for anyone */ +    for (int i = 0; i < 1000; i++) { +        char batpath[1024]; +        (void)snprintf(batpath, sizeof(batpath), path, i); + +        if (!strcmp(batpath, path)) { +            OUTPUT_FULL_TEXT("no '%d' in battery path"); +            return false; +        } + +        /* Probe to see if there is such a battery. */ +        struct stat sb; +        if (stat(batpath, &sb) != 0) { +            /* No such file, then we are done, assuming sysfs files have sequential numbers. */ +            if (errno == ENOENT) +                break; + +            OUTPUT_FULL_TEXT(format_down); +            return false; +        } + +        struct battery_info batt_buf = { +            .full_design = 0, +            .full_last = 0, +            .remaining = 0, +            .present_rate = 0, +            .status = CS_UNKNOWN, +        }; +        if (!slurp_battery_info(&batt_buf, json_gen, buffer, i, path, format_down)) +            return false; + +        is_found = true; +        add_battery_info(batt_info, &batt_buf);      } -    if (is_full) -        batt_info->status = CS_FULL; +    if (!is_found) { +        OUTPUT_FULL_TEXT(format_down); +        return false; +    } + +    batt_info->present_rate = abs(batt_info->present_rate); +#else +    /* FreeBSD and OpenBSD only report aggregates. NetBSD always +     * iterates through all batteries, so it's more efficient to +     * aggregate in slurp_battery_info. */ +    return slurp_battery_info(batt_info, json_gen, buffer, -1, path, format_down);  #endif      return true; @@ -324,10 +471,11 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char      struct battery_info batt_info = {          .full_design = -1,          .full_last = -1, +        .remaining = -1,          .present_rate = -1,          .seconds_remaining = -1,          .percentage_remaining = -1, -        .status = CS_DISCHARGING, +        .status = CS_UNKNOWN,      };      bool colorful_output = false; @@ -340,8 +488,13 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char      hide_seconds = true;  #endif -    if (!slurp_battery_info(&batt_info, json_gen, buffer, number, path, format_down)) -        return; +    if (number < 0) { +        if (!slurp_all_batteries(&batt_info, json_gen, buffer, path, format_down)) +            return; +    } else { +        if (!slurp_battery_info(&batt_info, json_gen, buffer, number, path, format_down)) +            return; +    }      int full = (last_full_capacity ? batt_info.full_last : batt_info.full_design);      if (full < 0 && batt_info.percentage_remaining < 0) { | 
