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) { |