diff options
33 files changed, 527 insertions, 110 deletions
diff --git a/.travis.yml b/.travis.yml index fc58769..71bc279 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,4 +22,4 @@ addons: script: - make -j - clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) - - ./travis/run-tests.pl + - make test @@ -1,3 +1,30 @@ +2018-05-11 i3status 2.12 + + • fix NULL value on OpenBSD when there is no acpitz0 + • show IP address when address has a label + • man: explicitly use format_up/format_down in ethernet, wireless conf + • man: remove SLOC comment + • treat zero battery capacity as "not available" + • add IPv6 address when IPv4 isn't available + • call tzset at each time print to pick up time zone changes + • doc: fixed minor typos + • be more cautious about handling invalid battery measurements. + • Makefile: make PKG_CONFIG overridable + • fix CPU unit tests + • use local timezone for battery %emptytime + • fix memory leak/use BEGINS_WITH macro + • _first_: add check for virtual ethernet devices + • add battery capacity parsing + • multiple CPU support for cpu_usage + • remove useless checks (we use char, not unsigned char) to fix compilation warnings + • disk: sanitize trailing slashes + • plug an fd leak in the OpenBSD-specific code for + • suppress printing :00 seconds of remaining battery lifetime, as apm(4)'s + estimate only has a granularity of minutes. + • fix the deciKelvin to Celsius conversion on FreeBSD + • make first_eth_interface() work on OpenBSD + • include sys/select.h on OpenBSD + 2017-01-21 i3status 2.11 • re-add forgotten wakeup call from SIGUSR1 handler diff --git a/I3STATUS_VERSION b/I3STATUS_VERSION index 0b08ad7..19462c2 100644 --- a/I3STATUS_VERSION +++ b/I3STATUS_VERSION @@ -1 +1 @@ -2.11-non-git +2.12-non-git @@ -99,6 +99,9 @@ i3status: ${OBJS} $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) @echo " LD $@" +test: i3status + LC_ALL=C ./travis/run-tests.pl + clean: rm -f *.o src/*.o @@ -113,8 +116,6 @@ install: install -m 755 -d $(DESTDIR)$(SYSCONFDIR) install -m 755 -d $(DESTDIR)$(MANPREFIX)/share/man/man1 install -m 755 i3status $(DESTDIR)$(PREFIX)/bin/i3status - # Allow network configuration for getting the link speed - (which setcap && setcap cap_net_admin=ep $(DESTDIR)$(PREFIX)/bin/i3status) || true install -m 644 i3status.conf $(DESTDIR)$(SYSCONFDIR)/i3status.conf install -m 644 man/i3status.1 $(DESTDIR)$(MANPREFIX)/share/man/man1 @@ -16,13 +16,12 @@ i3status has the following dependencies: * libyajl-dev * libasound2-dev * libnl-genl-3-dev - * libcap2-bin (for getting network status without root permissions) * asciidoc (only for the documentation) * libpulse-dev (for getting the current volume using PulseAudio) On debian-based systems, the following line will install all requirements: ```bash -apt-get install libconfuse-dev libyajl-dev libasound2-dev libiw-dev asciidoc libcap2-bin libpulse-dev libnl-genl-3-dev +apt-get install libconfuse-dev libyajl-dev libasound2-dev libiw-dev asciidoc libpulse-dev libnl-genl-3-dev ``` ## Upstream @@ -106,7 +106,7 @@ static bool path_exists(const char *path) { static void *scalloc(size_t size) { void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); + exit_if_null(result, "Error: out of memory (calloc(%zu))\n", size); return result; } @@ -142,7 +142,7 @@ static int parse_min_width(cfg_t *context, cfg_opt_t *option, const char *value, long num = strtol(value, &end, 10); if (num < 0) - die("Invalid min_width attribute found in section %s, line %d: %d\n" + die("Invalid min_width attribute found in section %s, line %d: %ld\n" "Expected positive integer or string\n", context->name, context->line, num); else if (num == LONG_MIN || num == LONG_MAX || (end && *end != '\0')) @@ -336,6 +336,7 @@ int main(int argc, char *argv[]) { cfg_opt_t wireless_opts[] = { CFG_STR("format_up", "W: (%quality at %essid, %bitrate) %ip", CFGF_NONE), CFG_STR("format_down", "W: down", CFGF_NONE), + CFG_STR("format_quality", "%3d%s", CFGF_NONE), CFG_CUSTOM_ALIGN_OPT, CFG_CUSTOM_COLOR_OPTS, CFG_CUSTOM_MIN_WIDTH_OPT, @@ -375,7 +376,7 @@ int main(int argc, char *argv[]) { CFG_STR("threshold_type", "time", CFGF_NONE), CFG_BOOL("last_full_capacity", false, CFGF_NONE), CFG_BOOL("integer_battery_capacity", false, CFGF_NONE), - CFG_BOOL("hide_seconds", false, CFGF_NONE), + CFG_BOOL("hide_seconds", true, CFGF_NONE), CFG_CUSTOM_ALIGN_OPT, CFG_CUSTOM_COLOR_OPTS, CFG_CUSTOM_MIN_WIDTH_OPT, @@ -421,6 +422,19 @@ int main(int argc, char *argv[]) { CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT, CFG_END()}; + cfg_opt_t memory_opts[] = { + CFG_STR("format", "%used %free %available", CFGF_NONE), + CFG_STR("format_degraded", NULL, CFGF_NONE), + CFG_STR("threshold_degraded", NULL, CFGF_NONE), + CFG_STR("threshold_critical", NULL, CFGF_NONE), + CFG_STR("memory_used_method", "classical", CFGF_NONE), + CFG_CUSTOM_ALIGN_OPT, + CFG_CUSTOM_COLOR_OPTS, + CFG_CUSTOM_MIN_WIDTH_OPT, + CFG_CUSTOM_SEPARATOR_OPT, + CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT, + CFG_END()}; + cfg_opt_t usage_opts[] = { CFG_STR("format", "%usage", CFGF_NONE), CFG_STR("format_above_threshold", NULL, CFGF_NONE), @@ -490,6 +504,7 @@ int main(int argc, char *argv[]) { CFG_SEC("tztime", tztime_opts, CFGF_TITLE | CFGF_MULTI), CFG_SEC("ddate", ddate_opts, CFGF_NONE), CFG_SEC("load", load_opts, CFGF_NONE), + CFG_SEC("memory", memory_opts, CFGF_NONE), CFG_SEC("cpu_usage", usage_opts, CFGF_NONE), CFG_END()}; @@ -631,6 +646,11 @@ int main(int argc, char *argv[]) { die("Could not create socket\n"); int interval = cfg_getint(cfg_general, "interval"); + if (interval <= 0) { + die("Invalid interval attribute found in section %s, line %d: %d\n" + "Expected positive integer\n", + cfg_general->name, cfg_general->line, interval); + } /* One memory page which each plugin can use to buffer output. * Even though it’s unclean, we just assume that the user will not @@ -674,7 +694,7 @@ int main(int argc, char *argv[]) { interface = first_eth_interface(NET_TYPE_WIRELESS); if (interface == NULL) interface = title; - print_wireless_info(json_gen, buffer, interface, cfg_getstr(sec, "format_up"), cfg_getstr(sec, "format_down")); + print_wireless_info(json_gen, buffer, interface, cfg_getstr(sec, "format_up"), cfg_getstr(sec, "format_down"), cfg_getstr(sec, "format_quality")); SEC_CLOSE_MAP; } @@ -719,6 +739,12 @@ int main(int argc, char *argv[]) { SEC_CLOSE_MAP; } + CASE_SEC("memory") { + SEC_OPEN_MAP("memory"); + print_memory(json_gen, buffer, cfg_getstr(sec, "format"), cfg_getstr(sec, "format_degraded"), cfg_getstr(sec, "threshold_degraded"), cfg_getstr(sec, "threshold_critical"), cfg_getstr(sec, "memory_used_method")); + SEC_CLOSE_MAP; + } + CASE_SEC("time") { SEC_OPEN_MAP("time"); print_time(json_gen, buffer, NULL, cfg_getstr(sec, "format"), NULL, NULL, NULL, tv.tv_sec); diff --git a/i3status.conf b/i3status.conf index 7f37964..6ac43cb 100644 --- a/i3status.conf +++ b/i3status.conf @@ -17,6 +17,7 @@ order += "wireless _first_" order += "ethernet _first_" order += "battery all" order += "load" +order += "memory" order += "tztime local" wireless _first_ { @@ -25,7 +26,6 @@ wireless _first_ { } ethernet _first_ { - # if you use %speed, i3status requires root privileges format_up = "E: %ip (%speed)" format_down = "E: down" } @@ -42,6 +42,12 @@ load { format = "%1min" } +memory { + format = "%used | %available" + threshold_degraded = "1G" + format_degraded = "MEMORY < %available" +} + disk "/" { format = "%avail" } diff --git a/include/i3status.h b/include/i3status.h index 9ac471d..e259987 100644 --- a/include/i3status.h +++ b/include/i3status.h @@ -186,7 +186,8 @@ char *sstrdup(const char *str); /* src/general.c */ char *skip_character(char *input, char character, int amount); -void die(const char *fmt, ...); + +void die(const char *fmt, ...) __attribute__((format(printf, 1, 2), noreturn)); bool slurp(const char *filename, char *destination, int size); /* src/output.c */ @@ -216,13 +217,14 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char void print_time(yajl_gen json_gen, char *buffer, const char *title, const char *format, const char *tz, const char *locale, const char *format_time, time_t t); void print_ddate(yajl_gen json_gen, char *buffer, const char *format, time_t t); const char *get_ip_addr(const char *interface, int family); -void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down); +void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down, const char *quality_min_lenght); void print_run_watch(yajl_gen json_gen, char *buffer, const char *title, const char *pidfile, const char *format, const char *format_down); void print_path_exists(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format, const char *format_down); void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const char *path, const char *format, const char *format_above_threshold, int); void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const char *format_above_degraded_threshold, const char *path, const float max_threshold, const float degraded_threshold); void print_eth_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down); void print_load(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const float max_threshold); +void print_memory(yajl_gen json_gen, char *buffer, const char *format, const char *format_degraded, const char *threshold_degraded, const char *threshold_critical, const char *memory_used_method); void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *fmt_muted, const char *device, const char *mixer, int mixer_idx); bool process_runs(const char *path); int volume_pulseaudio(uint32_t sink_idx, const char *sink_name); diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 4993579..459c639 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] <refentrytitle>{mantitle}</refentrytitle> <manvolnum>{manvolnum}</manvolnum> <refmiscinfo class="source">i3status</refmiscinfo> -<refmiscinfo class="version">v2.11</refmiscinfo> +<refmiscinfo class="version">v2.12</refmiscinfo> <refmiscinfo class="manual">i3 Manual</refmiscinfo> </refmeta> <refnamediv> diff --git a/man/i3status.man b/man/i3status.man index 9569828..f22e479 100644 --- a/man/i3status.man +++ b/man/i3status.man @@ -1,7 +1,7 @@ i3status(1) =========== Michael Stapelberg <michael@i3wm.org> -v2.11, January 2017 +v2.12, May 2018 == NAME @@ -24,13 +24,12 @@ configuration files in the following order: == DESCRIPTION -i3status is a small program (about 1500 SLOC) for generating a status bar for -i3bar, dzen2, xmobar, lemonbar or similar programs. It is designed to be very -efficient by issuing a very small number of system calls, as one generally -wants to update such a status line every second. This ensures that even under -high load, your status bar is updated correctly. Also, it saves a bit of energy -by not hogging your CPU as much as spawning the corresponding amount of shell -commands would. +i3status is a small program for generating a status bar for i3bar, dzen2, +xmobar, lemonbar or similar programs. It is designed to be very efficient by +issuing a very small number of system calls, as one generally wants to update +such a status line every second. This ensures that even under high load, your +status bar is updated correctly. Also, it saves a bit of energy by not hogging +your CPU as much as spawning the corresponding amount of shell commands would. == CONFIGURATION @@ -56,6 +55,7 @@ order += "wireless wlan0" order += "ethernet eth0" order += "battery 0" order += "cpu_temperature 0" +order += "memory" order += "load" order += "tztime local" order += "tztime berlin" @@ -66,7 +66,6 @@ wireless wlan0 { } ethernet eth0 { - # if you use %speed, i3status requires the cap_net_admin capability format_up = "E: %ip (%speed)" format_down = "E: down" } @@ -114,6 +113,12 @@ cpu_temperature 0 { path = "/sys/devices/platform/coretemp.0/temp1_input" } +memory { + format = "%used" + threshold_degraded = "10%" + format_degraded = "MEMORY: %free" +} + disk "/" { format = "%free" } @@ -154,7 +159,7 @@ etc.). dzen2:: Dzen is a general purpose messaging, notification and menuing program for X11. It was designed to be scriptable in any language and integrate well with window -managers like dwm, wmii and xmonad though it will work with any windowmanger +managers like dwm, wmii and xmonad though it will work with any window manager xmobar:: xmobar is a minimalistic, text based, status bar. It was designed to work with the xmonad Window Manager. @@ -193,6 +198,7 @@ double-quote (""") characters need to be replaced with "`&`", "`<`", for generated content (e.g. wireless ESSID, time). *Example configuration*: + ------------------------------------------------------------- general { output_format = "xmobar" @@ -293,28 +299,34 @@ There also is an option "format_down". You can hide the output with Gets the link quality, frequency and ESSID of the given wireless network interface. You can specify different format strings for the network being -connected or not connected. +connected or not connected. The quality is padded with leading zeroes by +default; to pad with something else use +format_quality+. The special interface name `_first_` will be replaced by the first wireless network interface found on the system (excluding devices starting with "lo"). *Example order*: +wireless wlan0+ -*Example format*: +W: (%quality at %essid, %bitrate / %frequency) %ip+ +*Example format_up*: +W: (%quality at %essid, %bitrate / %frequency) %ip+ + +*Example format_down*: +W: down+ + +*Example format_quality*: +"%03d%s"+ === Ethernet Gets the IP address and (if possible) the link speed of the given ethernet interface. If no IPv4 address is available and an IPv6 address is, it will be -displayed. Getting the link speed requires the cap_net_admin capability. -Set it using +setcap cap_net_admin=ep $(which i3status)+. +displayed. The special interface name `_first_` will be replaced by the first non-wireless network interface found on the system (excluding devices starting with "lo"). *Example order*: +ethernet eth0+ -*Example format*: +E: %ip (%speed)+ +*Example format_up*: +E: %ip (%speed)+ + +*Example format_down*: +E: down+ === Battery @@ -324,8 +336,8 @@ estimated to be empty. If you want to use the last full capacity instead of the design capacity (when using the design capacity, it may happen that your battery is at 23% when fully charged because it’s old. In general, I want to see it this way, because it tells me how worn off my battery is.), just specify -+last_full_capacity = true+. You can hide seconds in the remaining time and -empty time estimations by setting +hide_seconds = true+. ++last_full_capacity = true+. You can show seconds in the remaining time and +empty time estimations by setting +hide_seconds = false+. If you want the battery percentage to be shown without decimals, add +integer_battery_capacity = true+. @@ -425,6 +437,45 @@ starting from %cpu0. This feature is currently not supported in FreeBSD. *Example format_above_degraded_threshold*: +Warning above degraded threshold: %usage+ +=== Memory + +Gets the memory usage from system on a Linux system from +/proc/meminfo+. Other +systems are currently not supported. + +As format placeholders, +total+, +used+, +free+, +available+ and +shared+ are +available. These will print human readable values. It's also possible to prefix +the placeholders with +percentage_+ to get a value in percent. + +It's possible to define a +threshold_degraded+ and a +threshold_critical+ to +color the status bar output in yellow or red, if the available memory falls +below the given threshold. Possible values of the threshold can be any integer, +suffixed with an iec symbol (+T+, +G+, +M+, +K+). Alternatively, the integer +can be suffixed by a percent sign, which then rets evaluated relatively to +total memory. + +If the +format_degraded+ parameter is given and either the critical or the +degraded threshold applies, +format_degraded+ will get used as format string. +It acts equivalently to +format+. + +As Linux' meminfo doesn't expose the overall memory in use, there are multiple +methods to distinguish the actually used memory. + +*Example memory_used_method*: +memavailable+ ("total memory" - "MemAvailable", matches +free+ command) + +*Example memory_used_method*: +classical+ ("total memory" - "free" - "buffers" - "cache", matches gnome system monitor) + +*Example order*: +memory+ + +*Example format*: +%free %available (%used) / %total+ + +*Example format*: +%percentage_used used, %percentage_free free, %percentage_shared shared+ + +*Example threshold_degraded*: +10%+ + +*Example threshold_critical*: +5%+ + +*Example format_degraded*: +Memory LOW: %free+ + === Load Gets the system load (number of processes waiting for CPU time in the last @@ -599,7 +650,7 @@ disk "/" { == Using i3status with dzen2 After installing dzen2, you can directly use it with i3status. Just ensure that -+output_format+ is set to +dzen2+. ++output_format+ is set to +dzen2+. *Note*: +min_width+ is not supported. *Example for usage of i3status with dzen2*: -------------------------------------------------------------- @@ -611,32 +662,19 @@ i3status | dzen2 -fg white -ta r -w 1280 \ To get xmobar to start, you might need to copy the default configuration file to +~/.xmobarrc+. Also, ensure that the +output_format+ option for i3status -is set to +xmobar+. +is set to +xmobar+. *Note*: +min_width+ is not supported. *Example for usage of i3status with xmobar*: --------------------------------------------------------------------- i3status | xmobar -o -t "%StdinReader%" -c "[Run StdinReader]" --------------------------------------------------------------------- -== What about memory usage or CPU frequency? +== What about CPU frequency? -While talking about two specific things, please understand this section as a +While talking about specific things, please understand this section as a general explanation why your favorite information is not included in i3status. -Let’s talk about memory usage specifically. It is hard to measure memory in a -way which is accurate or meaningful. An in-depth understanding of how paging -and virtual memory work in your operating system is required. Furthermore, even -if we had a well-defined way of displaying memory usage and you would -understand it, I think that it’s not helpful to repeatedly monitor your memory -usage. One reason for that is that I have not run out of memory in the last few -years. Memory has become so cheap that even in my 4 year old notebook, I have -8 GiB of RAM. Another reason is that your operating system will do the right -thing anyway: Either you have not enough RAM for your workload, but you need to -do it anyway, then your operating system will swap. Or you don’t have enough -RAM and you want to restrict your workload so that it fits, then the operating -system will kill the process using too much RAM and you can act accordingly. - -For CPU frequency, the situation is similar. Many people don’t understand how +Let’s talk about CPU frequency specifically. Many people don’t understand how frequency scaling works precisely. The generally recommended CPU frequency governor ("ondemand") changes the CPU frequency far more often than i3status could display it. The display number is therefore often incorrect and doesn’t @@ -648,10 +686,9 @@ a WiFi network or not, and if you have enough disk space to fit that 4.3 GiB download. However, if you need to look at some kind of information more than once in a -while (like checking repeatedly how full your RAM is), you are probably better -off with a script doing that, which pops up an alert when your RAM usage reaches -a certain threshold. After all, the point of computers is not to burden you -with additional boring tasks like repeatedly checking a number. +while, you are probably better off with a script doing that, which pops up. +After all, the point of computers is not to burden you with additional boring +tasks like repeatedly checking a number. == External scripts/programs with i3status diff --git a/src/first_network_device.c b/src/first_network_device.c index 3f34cf2..b930f53 100644 --- a/src/first_network_device.c +++ b/src/first_network_device.c @@ -14,10 +14,10 @@ #include "i3status.h" -#ifdef __linux__ -#define LOOPBACK_DEV "lo" -#else +#ifdef __OpenBSD__ #define LOOPBACK_DEV "lo0" +#else +#define LOOPBACK_DEV "lo" #endif static bool sysfs_devtype(char *dest, size_t n, const char *ifnam) { @@ -67,24 +67,7 @@ static bool is_virtual(const char *ifname) { } static net_type_t iface_type(const char *ifname) { -#ifdef __linux__ - char devtype[32]; - - if (!sysfs_devtype(devtype, sizeof(devtype), ifname)) - return NET_TYPE_OTHER; - - /* Default to Ethernet when no devtype is available */ - if (!devtype[0]) - return NET_TYPE_ETHERNET; - - if (strcmp(devtype, "wlan") == 0) - return NET_TYPE_WIRELESS; - - if (strcmp(devtype, "wwan") == 0) - return NET_TYPE_OTHER; - - return NET_TYPE_OTHER; -#elif __OpenBSD__ +#ifdef __OpenBSD__ /* *First determine if the device is a wireless device by trying two ioctl(2) * commands against it. If either succeeds we can be sure it's a wireless @@ -127,8 +110,24 @@ static net_type_t iface_type(const char *ifname) { return NET_TYPE_ETHERNET; } #else -#error Missing implementation to determine interface type. + char devtype[32]; + + if (!sysfs_devtype(devtype, sizeof(devtype), ifname)) + return NET_TYPE_OTHER; + + /* Default to Ethernet when no devtype is available */ + if (!devtype[0]) + return NET_TYPE_ETHERNET; + + if (strcmp(devtype, "wlan") == 0) + return NET_TYPE_WIRELESS; + + if (strcmp(devtype, "wwan") == 0) + return NET_TYPE_OTHER; + + return NET_TYPE_OTHER; #endif + return NET_TYPE_OTHER; } const char *first_eth_interface(const net_type_t type) { diff --git a/src/general.c b/src/general.c index f299a2b..2424cc6 100644 --- a/src/general.c +++ b/src/general.c @@ -51,12 +51,10 @@ char *skip_character(char *input, char character, int amount) { * */ void die(const char *fmt, ...) { - char buffer[512]; va_list ap; va_start(ap, fmt); - (void)vsnprintf(buffer, sizeof(buffer), fmt, ap); + (void)vfprintf(stderr, fmt, ap); va_end(ap); - fprintf(stderr, "%s", buffer); exit(EXIT_FAILURE); } diff --git a/src/print_battery_info.c b/src/print_battery_info.c index fa70714..db4d7bd 100644 --- a/src/print_battery_info.c +++ b/src/print_battery_info.c @@ -497,14 +497,17 @@ static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_ge .present_rate = 0, .status = CS_UNKNOWN, }; - if (!slurp_battery_info(&batt_buf, json_gen, buffer, i, globbuf.gl_pathv[i], format_down)) + if (!slurp_battery_info(&batt_buf, json_gen, buffer, i, globbuf.gl_pathv[i], format_down)) { + globfree(&globbuf); + free(globpath); return false; + } is_found = true; add_battery_info(batt_info, &batt_buf); } + globfree(&globbuf); } - globfree(&globbuf); free(globpath); if (!is_found) { @@ -560,13 +563,13 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char // We prefer the design capacity, but use the last capacity if we don't have it, // or if we are asked to (last_full_capacity == true); but similarly we use // the design capacity if we don't have the last capacity. - // If we don't have either then both full_design and full_last < 0, - // which implies full < 0, which bails out on the following line. + // If we don't have either then both full_design and full_last <= 0, + // which implies full <= 0, which bails out on the following line. int full = batt_info.full_design; - if (full < 0 || (last_full_capacity && batt_info.full_last >= 0)) { + if (full <= 0 || (last_full_capacity && batt_info.full_last > 0)) { full = batt_info.full_last; } - if (full < 0 && batt_info.remaining < 0 && batt_info.percentage_remaining < 0) { + if (full <= 0 && batt_info.remaining < 0 && batt_info.percentage_remaining < 0) { /* We have no physical measurements and no estimates. Nothing * much we can report, then. */ OUTPUT_FULL_TEXT(format_down); diff --git a/src/print_cpu_temperature.c b/src/print_cpu_temperature.c index 56ab62a..feae3ec 100644 --- a/src/print_cpu_temperature.c +++ b/src/print_cpu_temperature.c @@ -112,7 +112,7 @@ static int read_temperature(char *thermal_zone, temperature_t *temperature) { /* 'path' is the node within the full path (defaults to acpitz0). */ if (BEGINS_WITH(sensordev.xname, thermal_zone)) { mib[3] = SENSOR_TEMP; - /* Limit to temo0, but should retrieve from a full path... */ + /* Limit to temp0, but should retrieve from a full path... */ for (numt = 0; numt < 1 /*sensordev.maxnumt[SENSOR_TEMP]*/; numt++) { mib[4] = numt; if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { @@ -205,7 +205,7 @@ error_netbsd1: /* * Reads the CPU temperature from /sys/class/thermal/thermal_zone%d/temp (or - * the user provided path) and returns the temperature in degree celcius. + * the user provided path) and returns the temperature in degree celsius. * */ void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const char *path, const char *format, const char *format_above_threshold, int max_threshold) { @@ -216,12 +216,14 @@ void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const bool colorful_output = false; char *thermal_zone; temperature_t temperature; + temperature.raw_value = 0; + sprintf(temperature.formatted_value, "%.2f", 0.0); if (path == NULL) asprintf(&thermal_zone, THERMAL_ZONE, zone); else { static glob_t globbuf; - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) != 0) die("glob() failed\n"); if (globbuf.gl_pathc == 0) { /* No glob matches, the specified path does not contain a wildcard. */ diff --git a/src/print_disk_info.c b/src/print_disk_info.c index fb73480..770e718 100644 --- a/src/print_disk_info.c +++ b/src/print_disk_info.c @@ -7,7 +7,7 @@ #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/types.h> -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || (__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__) #include <sys/param.h> #include <sys/mount.h> #elif defined(__NetBSD__) diff --git a/src/print_eth_info.c b/src/print_eth_info.c index b30d2b0..996ce3b 100644 --- a/src/print_eth_info.c +++ b/src/print_eth_info.c @@ -33,7 +33,6 @@ static int print_eth_speed(char *outwalk, const char *interface) { #if defined(LINUX) - /* This code path requires root privileges */ int ethspeed = 0; struct ifreq ifr; struct ethtool_cmd ecmd; diff --git a/src/print_ip_addr.c b/src/print_ip_addr.c index f9cd1f4..30a1ce3 100644 --- a/src/print_ip_addr.c +++ b/src/print_ip_addr.c @@ -13,6 +13,31 @@ #include "i3status.h" /* + * Return a copy of the .ifa_name field passed as argument where the optional + * IP label, if present, is removed. + * + * example: + * - strip_optional_label("eth0") => "eth0" + * - strip_optional_label("eth0:label") => "eth0" + * + * The memory for the returned string is obtained with malloc(3), and can be + * freed with free(3). + * + * + */ +static char *strip_optional_label(const char *ifa_name) { + char *copy = sstrdup(ifa_name); + + char *ptr = strchr(copy, ':'); + + if (ptr) { + *ptr = '\0'; + } + + return copy; +} + +/* * Return the IP address for the given interface or "no IP" if the * interface is up and running but hasn't got an IP address yet * @@ -36,22 +61,30 @@ const char *get_ip_addr(const char *interface, int family) { return NULL; /* Skip until we are at the input family address of interface */ - for (addrp = ifaddr; - - (addrp != NULL && - (strcmp(addrp->ifa_name, interface) != 0 || - addrp->ifa_addr == NULL || - addrp->ifa_addr->sa_family != family)); + for (addrp = ifaddr; addrp != NULL; addrp = addrp->ifa_next) { + /* Strip the label if present in the .ifa_name field. */ + char *stripped_ifa_name = strip_optional_label(addrp->ifa_name); - addrp = addrp->ifa_next) { - /* Check if the interface is down */ - if (strcmp(addrp->ifa_name, interface) != 0) + bool name_matches = strcmp(stripped_ifa_name, interface) != 0; + free(stripped_ifa_name); + if (name_matches) { + /* The interface does not have the right name, skip it. */ continue; - found = true; + } + + if (addrp->ifa_addr != NULL && addrp->ifa_addr->sa_family == family) { + /* We found the right interface with the right address. */ + break; + } + + /* Check if the interface is down. If it is, no need to look any + * further. */ if ((addrp->ifa_flags & IFF_RUNNING) == 0) { freeifaddrs(ifaddr); return NULL; } + + found = true; } if (addrp == NULL) { diff --git a/src/print_mem.c b/src/print_mem.c new file mode 100644 index 0000000..a37fa29 --- /dev/null +++ b/src/print_mem.c @@ -0,0 +1,224 @@ +// vim:ts=4:sw=4:expandtab +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <yajl/yajl_gen.h> +#include <yajl/yajl_version.h> +#include "i3status.h" + +#define BINARY_BASE UINT64_C(1024) + +#define MAX_EXPONENT 4 +static const char *const iec_symbols[MAX_EXPONENT + 1] = {"", "Ki", "Mi", "Gi", "Ti"}; + +static const char memoryfile_linux[] = "/proc/meminfo"; + +/* + * Prints the given amount of bytes in a human readable manner. + * + */ +static int print_bytes_human(char *outwalk, uint64_t bytes) { + double size = bytes; + int exponent = 0; + int bin_base = BINARY_BASE; + while (size >= bin_base && exponent < MAX_EXPONENT) { + size /= bin_base; + exponent += 1; + } + return sprintf(outwalk, "%.1f %sB", size, iec_symbols[exponent]); +} + +/* + * Convert a string to its absolute representation based on the total + * memory of `mem_total`. + * + * The string can contain any percentage values, which then return a + * the value of `size` in relation to `mem_total`. + * Alternatively an absolute value can be given, suffixed with an iec + * symbol. + * + */ +static long memory_absolute(const long mem_total, const char *size) { + char *endptr = NULL; + + long mem_absolute = strtol(size, &endptr, 10); + + if (endptr) { + while (endptr[0] != '\0' && isspace(endptr[0])) + endptr++; + + switch (endptr[0]) { + case 'T': + case 't': + mem_absolute *= BINARY_BASE; + case 'G': + case 'g': + mem_absolute *= BINARY_BASE; + case 'M': + case 'm': + mem_absolute *= BINARY_BASE; + case 'K': + case 'k': + mem_absolute *= BINARY_BASE; + break; + case '%': + mem_absolute = mem_total * mem_absolute / 100; + break; + default: + break; + } + } + + return mem_absolute; +} + +void print_memory(yajl_gen json_gen, char *buffer, const char *format, const char *format_degraded, const char *threshold_degraded, const char *threshold_critical, const char *memory_used_method) { + char *outwalk = buffer; + +#if defined(linux) + const char *selected_format = format; + const char *walk; + const char *output_color = NULL; + + long ram_total = -1; + long ram_free = -1; + long ram_available = -1; + long ram_used = -1; + long ram_shared = -1; + long ram_cached = -1; + long ram_buffers = -1; + + FILE *file = fopen(memoryfile_linux, "r"); + if (!file) { + goto error; + } + char line[128]; + while (fgets(line, sizeof line, file)) { + if (BEGINS_WITH(line, "MemTotal:")) { + ram_total = strtol(line + strlen("MemTotal:"), NULL, 10); + } + if (BEGINS_WITH(line, "MemFree:")) { + ram_free = strtol(line + strlen("MemFree:"), NULL, 10); + } + if (BEGINS_WITH(line, "MemAvailable:")) { + ram_available = strtol(line + strlen("MemAvailable:"), NULL, 10); + } + if (BEGINS_WITH(line, "Buffers:")) { + ram_buffers = strtol(line + strlen("Buffers:"), NULL, 10); + } + if (BEGINS_WITH(line, "Cached:")) { + ram_cached = strtol(line + strlen("Cached:"), NULL, 10); + } + if (BEGINS_WITH(line, "Shmem:")) { + ram_shared = strtol(line + strlen("Shmem:"), NULL, 10); + } + if (ram_total != -1 && ram_free != -1 && ram_available != -1 && ram_buffers != -1 && ram_cached != -1 && ram_shared != -1) { + break; + } + } + fclose(file); + + if (ram_total == -1 || ram_free == -1 || ram_available == -1 || ram_buffers == -1 || ram_cached == -1 || ram_shared == -1) { + goto error; + } + + ram_total = ram_total * BINARY_BASE; + ram_free = ram_free * BINARY_BASE; + ram_available = ram_available * BINARY_BASE; + ram_buffers = ram_buffers * BINARY_BASE; + ram_cached = ram_cached * BINARY_BASE; + ram_shared = ram_shared * BINARY_BASE; + + if (BEGINS_WITH(memory_used_method, "memavailable")) { + ram_used = ram_total - ram_available; + } else if (BEGINS_WITH(memory_used_method, "classical")) { + ram_used = ram_total - ram_free - ram_buffers - ram_cached; + } + + if (threshold_degraded) { + long abs = memory_absolute(ram_total, threshold_degraded); + if (ram_available < abs) { + output_color = "color_degraded"; + } + } + + if (threshold_critical) { + long abs = memory_absolute(ram_total, threshold_critical); + if (ram_available < abs) { + output_color = "color_bad"; + } + } + + if (output_color) { + START_COLOR(output_color); + + if (format_degraded) + selected_format = format_degraded; + } + + for (walk = selected_format; *walk != '\0'; walk++) { + if (*walk != '%') { + *(outwalk++) = *walk; + continue; + } + if (BEGINS_WITH(walk + 1, "total")) { + outwalk += print_bytes_human(outwalk, ram_total); + walk += strlen("total"); + } + + if (BEGINS_WITH(walk + 1, "used")) { + outwalk += print_bytes_human(outwalk, ram_used); + walk += strlen("used"); + } + + if (BEGINS_WITH(walk + 1, "free")) { + outwalk += print_bytes_human(outwalk, ram_free); + walk += strlen("free"); + } + + if (BEGINS_WITH(walk + 1, "available")) { + outwalk += print_bytes_human(outwalk, ram_available); + walk += strlen("available"); + } + + if (BEGINS_WITH(walk + 1, "shared")) { + outwalk += print_bytes_human(outwalk, ram_shared); + walk += strlen("shared"); + } + + if (BEGINS_WITH(walk + 1, "percentage_free")) { + outwalk += sprintf(outwalk, "%.01f%s", 100.0 * ram_free / ram_total, pct_mark); + walk += strlen("percentage_free"); + } + + if (BEGINS_WITH(walk + 1, "percentage_available")) { + outwalk += sprintf(outwalk, "%.01f%s", 100.0 * ram_available / ram_total, pct_mark); + walk += strlen("percentage_available"); + } + + if (BEGINS_WITH(walk + 1, "percentage_used")) { + outwalk += sprintf(outwalk, "%.01f%s", 100.0 * ram_used / ram_total, pct_mark); + walk += strlen("percentage_used"); + } + + if (BEGINS_WITH(walk + 1, "percentage_shared")) { + outwalk += sprintf(outwalk, "%.01f%s", 100.0 * ram_shared / ram_total, pct_mark); + walk += strlen("percentage_shared"); + } + } + + if (output_color) + END_COLOR; + + *outwalk = '\0'; + OUTPUT_FULL_TEXT(buffer); + + return; +error: + OUTPUT_FULL_TEXT("can't read memory"); + fputs("i3status: Cannot read system memory using /proc/meminfo\n", stderr); +#else + OUTPUT_FULL_TEXT(""); + fputs("i3status: Memory status information is not supported on this system\n", stderr); +#endif +} diff --git a/src/print_volume.c b/src/print_volume.c index 51e84f3..e28a132 100644 --- a/src/print_volume.c +++ b/src/print_volume.c @@ -151,7 +151,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * snd_mixer_selem_id_set_index(sid, mixer_idx); snd_mixer_selem_id_set_name(sid, mixer); if (!(elem = snd_mixer_find_selem(m, sid))) { - fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n", + fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %u)\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); snd_mixer_close(m); snd_mixer_selem_id_free(sid); @@ -211,6 +211,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * #if defined(__OpenBSD__) int oclass_idx = -1, master_idx = -1, master_mute_idx = -1; + int master_next = AUDIO_MIXER_LAST; mixer_devinfo_t devinfo, devinfo2; mixer_ctrl_t vinfo; @@ -228,12 +229,17 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * devinfo2.index = 0; while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo2) >= 0) { - if ((devinfo2.type == AUDIO_MIXER_VALUE) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmaster, MAX_AUDIO_DEV_LEN) == 0)) + if ((devinfo2.type == AUDIO_MIXER_VALUE) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmaster, MAX_AUDIO_DEV_LEN) == 0)) { master_idx = devinfo2.index; + master_next = devinfo2.next; + } if ((devinfo2.type == AUDIO_MIXER_ENUM) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmute, MAX_AUDIO_DEV_LEN) == 0)) - master_mute_idx = devinfo2.index; + if (master_next == devinfo2.index) + master_mute_idx = devinfo2.index; + if (master_next != AUDIO_MIXER_LAST) + master_next = devinfo2.next; devinfo2.index++; } @@ -246,6 +252,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * vinfo.dev = master_idx; vinfo.type = AUDIO_MIXER_VALUE; + vinfo.un.value.num_channels = devinfo.un.v.num_channels; if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1) goto out; diff --git a/src/print_wireless_info.c b/src/print_wireless_info.c index c3b5270..dcfde52 100644 --- a/src/print_wireless_info.c +++ b/src/print_wireless_info.c @@ -16,6 +16,7 @@ #endif #ifdef __APPLE__ +#include <sys/socket.h> #define IW_ESSID_MAX_SIZE 32 #endif @@ -479,7 +480,7 @@ error1: * | 127.0.0.1 | no IP | IPv4 | ok | * | 127.0.0.1 | ::1/128 | IPv4 | ok | */ -void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down) { +void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down, const char *format_quality) { const char *walk; char *outwalk = buffer; wireless_info_t info; @@ -539,7 +540,7 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, if (BEGINS_WITH(walk + 1, "quality")) { if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) { if (info.quality_max) - outwalk += sprintf(outwalk, "%3d%s", PERCENT_VALUE(info.quality, info.quality_max), pct_mark); + outwalk += sprintf(outwalk, format_quality, PERCENT_VALUE(info.quality, info.quality_max), pct_mark); else outwalk += sprintf(outwalk, "%d", info.quality); } else { diff --git a/src/process_runs.c b/src/process_runs.c index b5e8f11..da2dba1 100644 --- a/src/process_runs.c +++ b/src/process_runs.c @@ -24,7 +24,7 @@ bool process_runs(const char *path) { static glob_t globbuf; memset(pidbuf, 0, sizeof(pidbuf)); - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) != 0) die("glob() failed\n"); if (globbuf.gl_pathc == 0) { /* No glob matches, the specified path does not contain a wildcard. */ diff --git a/testcases/001-battery/expected_output.txt b/testcases/001-battery/expected_output.txt index 6e1898b..ab4bc9f 100644 --- a/testcases/001-battery/expected_output.txt +++ b/testcases/001-battery/expected_output.txt @@ -1 +1 @@ -BAT 30.64% 02:09:32 +BAT 30.64% 02:09 diff --git a/testcases/015-battery-capacity/expected_output.txt b/testcases/015-battery-capacity/expected_output.txt index 663a593..fb37af8 100644 --- a/testcases/015-battery-capacity/expected_output.txt +++ b/testcases/015-battery-capacity/expected_output.txt @@ -1 +1 @@ -Touchpad: 30.64% BAT 02:09:32 +Touchpad: 30.64% BAT 02:09 diff --git a/testcases/018-battery-capacity/1/uevent b/testcases/018-battery-capacity/1/uevent new file mode 100644 index 0000000..00b1148 --- /dev/null +++ b/testcases/018-battery-capacity/1/uevent @@ -0,0 +1,2 @@ +POWER_SUPPLY_CHARGE_FULL=100 +POWER_SUPPLY_CHARGE_NOW=50 diff --git a/testcases/018-battery-capacity/expected_output.txt b/testcases/018-battery-capacity/expected_output.txt new file mode 100644 index 0000000..ab07d0a --- /dev/null +++ b/testcases/018-battery-capacity/expected_output.txt @@ -0,0 +1 @@ +50.00% diff --git a/testcases/018-battery-capacity/i3status.conf b/testcases/018-battery-capacity/i3status.conf new file mode 100644 index 0000000..7a3443c --- /dev/null +++ b/testcases/018-battery-capacity/i3status.conf @@ -0,0 +1,10 @@ +general { + output_format = "none" +} + +order += "battery all" + +battery all { + format = "%percentage" + path = "testcases/018-battery-capacity/%d/uevent" +} diff --git a/testcases/019-battery-capacity/1/uevent b/testcases/019-battery-capacity/1/uevent new file mode 100644 index 0000000..d004bf1 --- /dev/null +++ b/testcases/019-battery-capacity/1/uevent @@ -0,0 +1,2 @@ +POWER_SUPPLY_CHARGE_FULL_DESIGN=100 +POWER_SUPPLY_CHARGE_NOW=50 diff --git a/testcases/019-battery-capacity/expected_output.txt b/testcases/019-battery-capacity/expected_output.txt new file mode 100644 index 0000000..ab07d0a --- /dev/null +++ b/testcases/019-battery-capacity/expected_output.txt @@ -0,0 +1 @@ +50.00% diff --git a/testcases/019-battery-capacity/i3status.conf b/testcases/019-battery-capacity/i3status.conf new file mode 100644 index 0000000..8152d44 --- /dev/null +++ b/testcases/019-battery-capacity/i3status.conf @@ -0,0 +1,11 @@ +general { + output_format = "none" +} + +order += "battery all" + +battery all { + format = "%percentage" + path = "testcases/019-battery-capacity/%d/uevent" + last_full_capacity = true +} diff --git a/testcases/021-battery-second/BAT0_uevent b/testcases/021-battery-second/BAT0_uevent new file mode 100644 index 0000000..b994324 --- /dev/null +++ b/testcases/021-battery-second/BAT0_uevent @@ -0,0 +1,4 @@ +POWER_SUPPLY_STATUS=Discharging +POWER_SUPPLY_CURRENT_NOW=1107000 +POWER_SUPPLY_CHARGE_FULL_DESIGN=7800000 +POWER_SUPPLY_CHARGE_NOW=2390000 diff --git a/testcases/021-battery-second/expected_output.txt b/testcases/021-battery-second/expected_output.txt new file mode 100644 index 0000000..6e1898b --- /dev/null +++ b/testcases/021-battery-second/expected_output.txt @@ -0,0 +1 @@ +BAT 30.64% 02:09:32 diff --git a/testcases/021-battery-second/i3status.conf b/testcases/021-battery-second/i3status.conf new file mode 100644 index 0000000..3aa2262 --- /dev/null +++ b/testcases/021-battery-second/i3status.conf @@ -0,0 +1,11 @@ +general { + output_format = "none" +} + +order += "battery all" + +battery all { + format = "%status %percentage %remaining" + path = "testcases/021-battery-second/BAT%d_uevent" + hide_seconds = false +} diff --git a/travis/run-tests.pl b/travis/run-tests.pl index 5936b7e..c335ff3 100755 --- a/travis/run-tests.pl +++ b/travis/run-tests.pl @@ -16,6 +16,7 @@ sub TestCase { my $conf = "$dir/i3status.conf"; my $testres = `./i3status --run-once -c $conf`; + my $exitcode = $?; my $refres = ""; if ( -f "@_/expected_output.txt") { @@ -28,24 +29,33 @@ sub TestCase { system($EXECUTABLE_NAME, "@_/cleanup.pl", ($dir)); } + if ( $exitcode != 0 ) { + say "Testing test case '", basename($dir), "'… ", BOLD, RED, "Crash!", RESET; + return 0; + } + if ( "$testres" eq "$refres" ) { say "Testing test case '", basename($dir), "'… ", BOLD, GREEN, "OK", RESET; return 1; } else { say "Testing test case '", basename($dir), "'… ", BOLD, RED, "Failed!", RESET; + say "Expected: '$refres'"; + say "Got: '$testres'"; return 0; } } my $testcases = 'testcases'; -my $testresults = 1; +my $testresults = 0; opendir(my $dir, $testcases) or die "Could not open directory $testcases: $!"; while (my $entry = readdir($dir)) { next unless (-d "$testcases/$entry"); next if ($entry =~ m/^\./); - $testresults = $testresults && TestCase("$testcases/$entry"); + if (not TestCase("$testcases/$entry") ) { + $testresults = 1; + } } closedir($dir); -exit 0; +exit $testresults; |