├── .gitignore ├── man ├── Makefile ├── asciidoc.conf ├── i3status.man └── i3status.1 ├── yajl-fallback └── yajl │ └── yajl_version.h ├── contrib ├── measure-net-speed-i3status.bash ├── wrapper.pl ├── measure-net-speed.bash └── wrapper.py ├── src ├── process_runs.c ├── print_run_watch.c ├── print_path_exists.c ├── print_time.c ├── general.c ├── print_ip_addr.c ├── print_load.c ├── output.c ├── print_cpu_usage.c ├── print_disk_info.c ├── print_eth_info.c ├── auto_detect_format.c ├── print_volume.c ├── print_mpd.c ├── print_ipv6_addr.c ├── print_ddate.c ├── print_cpu_temperature.c ├── print_wireless_info.c └── print_battery_info.c ├── i3status.conf ├── README.md ├── LICENSE ├── Makefile ├── include ├── i3status.h └── queue.h ├── CHANGELOG └── i3status.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .*.swp 3 | i3status 4 | -------------------------------------------------------------------------------- /man/Makefile: -------------------------------------------------------------------------------- 1 | all: i3status.1 2 | 3 | A2X?=a2x 4 | 5 | i3status.1: asciidoc.conf i3status.man 6 | ${A2X} -f manpage --asciidoc-opts="-f asciidoc.conf" i3status.man 7 | clean: 8 | rm -f i3status.xml i3status.1 i3status.html 9 | -------------------------------------------------------------------------------- /yajl-fallback/yajl/yajl_version.h: -------------------------------------------------------------------------------- 1 | #ifndef YAJL_VERSION_H_ 2 | #define YAJL_VERSION_H_ 3 | /* Fallback for libyajl 1 which does not provide yajl_version.h */ 4 | #define YAJL_MAJOR 1 5 | #define YAJL_MINOR 0 6 | #define YAJL_MICRO 0 7 | #endif 8 | -------------------------------------------------------------------------------- /man/asciidoc.conf: -------------------------------------------------------------------------------- 1 | ifdef::doctype-manpage[] 2 | ifdef::backend-docbook[] 3 | [header] 4 | template::[header-declarations] 5 | 6 | 7 | {mantitle} 8 | {manvolnum} 9 | i3status 10 | v2.8 11 | i3 Manual 12 | 13 | 14 | {manname} 15 | {manpurpose} 16 | 17 | endif::backend-docbook[] 18 | endif::doctype-manpage[] 19 | -------------------------------------------------------------------------------- /contrib/measure-net-speed-i3status.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Public Domain 3 | # (someone claimed the next lines would be useful for… 4 | # people. So here goes: © 2012 Stefan Breunig 5 | # stefan+measure-net-speed@mathphys.fsk.uni-heidelberg.de) 6 | 7 | # append i3status output to the measure-net-speed’s one. 8 | # the quote and escape magic is required to get valid 9 | # JSON output, which is expected by i3bar (if you want 10 | # colors, that is. Otherwise plain text would be fine). 11 | # For colors, your i3status.conf should contain: 12 | # general { 13 | # output_format = i3bar 14 | # } 15 | 16 | # i3 config looks like this: 17 | # bar { 18 | # status_command measure-net-speed-i3status.bash 19 | # } 20 | 21 | i3status | (read line && echo $line && read line && echo $line && while : 22 | do 23 | read line 24 | dat=$(measure-net-speed.bash) 25 | dat="[{ \"full_text\": \"${dat}\" }," 26 | echo "${line/[/$dat}" || exit 1 27 | done) -------------------------------------------------------------------------------- /src/process_runs.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "i3status.h" 12 | 13 | /* 14 | * Checks if the PID in path is still valid by sending signal 0 (does not do 15 | * anything). kill() will return ESRCH if the process does not exist and 0 or 16 | * EPERM (depending on the uid) if it exists. 17 | * 18 | */ 19 | bool process_runs(const char *path) { 20 | static char pidbuf[16]; 21 | static glob_t globbuf; 22 | memset(pidbuf, 0, sizeof(pidbuf)); 23 | 24 | if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) 25 | die("glob() failed\n"); 26 | if (!slurp((globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path), pidbuf, sizeof(pidbuf))) { 27 | globfree(&globbuf); 28 | return false; 29 | } 30 | globfree(&globbuf); 31 | 32 | return (kill(strtol(pidbuf, NULL, 10), 0) == 0 || errno == EPERM); 33 | } 34 | -------------------------------------------------------------------------------- /src/print_run_watch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "i3status.h" 6 | 7 | void print_run_watch(yajl_gen json_gen, char *buffer, const char *title, const char *pidfile, const char *format) { 8 | bool running = process_runs(pidfile); 9 | const char *walk; 10 | char *outwalk = buffer; 11 | 12 | INSTANCE(pidfile); 13 | 14 | START_COLOR((running ? "color_good" : "color_bad")); 15 | 16 | for (walk = format; *walk != '\0'; walk++) { 17 | if (*walk != '%') { 18 | *(outwalk++) = *walk; 19 | continue; 20 | } 21 | 22 | if (strncmp(walk+1, "title", strlen("title")) == 0) { 23 | outwalk += sprintf(outwalk, "%s", title); 24 | walk += strlen("title"); 25 | } else if (strncmp(walk+1, "status", strlen("status")) == 0) { 26 | outwalk += sprintf(outwalk, "%s", (running ? "yes" : "no")); 27 | walk += strlen("status"); 28 | } 29 | } 30 | 31 | END_COLOR; 32 | OUTPUT_FULL_TEXT(buffer); 33 | } 34 | -------------------------------------------------------------------------------- /src/print_path_exists.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "i3status.h" 7 | 8 | void print_path_exists(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format) { 9 | const char *walk; 10 | char *outwalk = buffer; 11 | struct stat st; 12 | const bool exists = (stat(path, &st) == 0); 13 | 14 | INSTANCE(path); 15 | 16 | START_COLOR((exists ? "color_good" : "color_bad")); 17 | 18 | for (walk = format; *walk != '\0'; walk++) { 19 | if (*walk != '%') { 20 | *(outwalk++) = *walk; 21 | continue; 22 | } 23 | 24 | if (strncmp(walk+1, "title", strlen("title")) == 0) { 25 | outwalk += sprintf(outwalk, "%s", title); 26 | walk += strlen("title"); 27 | } else if (strncmp(walk+1, "status", strlen("status")) == 0) { 28 | outwalk += sprintf(outwalk, "%s", (exists ? "yes" : "no")); 29 | walk += strlen("status"); 30 | } 31 | } 32 | 33 | END_COLOR; 34 | OUTPUT_FULL_TEXT(buffer); 35 | } 36 | -------------------------------------------------------------------------------- /i3status.conf: -------------------------------------------------------------------------------- 1 | # i3status configuration file. 2 | # see "man i3status" for documentation. 3 | 4 | # It is important that this file is edited as UTF-8. 5 | # The following line should contain a sharp s: 6 | # ß 7 | # If the above line is not correctly displayed, fix your editor first! 8 | 9 | general { 10 | colors = true 11 | interval = 5 12 | } 13 | 14 | order += "ipv6" 15 | order += "disk /" 16 | order += "run_watch DHCP" 17 | order += "run_watch VPN" 18 | order += "wireless wlan0" 19 | order += "ethernet eth0" 20 | order += "battery 0" 21 | order += "load" 22 | order += "tztime local" 23 | 24 | wireless wlan0 { 25 | format_up = "W: (%quality at %essid) %ip" 26 | format_down = "W: down" 27 | } 28 | 29 | ethernet eth0 { 30 | # if you use %speed, i3status requires root privileges 31 | format_up = "E: %ip (%speed)" 32 | format_down = "E: down" 33 | } 34 | 35 | battery 0 { 36 | format = "%status %percentage %remaining" 37 | } 38 | 39 | run_watch DHCP { 40 | pidfile = "/var/run/dhclient*.pid" 41 | } 42 | 43 | run_watch VPN { 44 | pidfile = "/var/run/vpnc/pid" 45 | } 46 | 47 | tztime local { 48 | format = "%Y-%m-%d %H:%M:%S" 49 | } 50 | 51 | load { 52 | format = "%1min" 53 | } 54 | 55 | disk "/" { 56 | format = "%avail" 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | i3status - glittershark's fork 2 | ============================== 3 | 4 | i3status is a small program for generating a status bar for i3bar, dzen2, 5 | xmobar, or similar programs. It is designed to be very efficient by issuing a 6 | very small number of system calls, as one generally wants to update such a 7 | status line every second. This ensures that even under high load, your status 8 | bar is updated correctly. Also, it saves a bit of energy by not hogging your 9 | CPU as much as spawning the corresponding amount of shell commands would. 10 | 11 | ### This fork adds: 12 | 13 | - Configurable display of the currently playing MPD track 14 | - Notifications of MPD track changes using libnotify 15 | - Notifications of battery state change and critical level with libnotify 16 | 17 | ## Installation 18 | 19 | Currently there is no official package for this fork of i3status, so you'll have 20 | to compile it yourself. Fortunately that's pretty easy - just clone the 21 | repository and run the following: 22 | 23 | ``` 24 | make && sudo make install 25 | ``` 26 | 27 | ## Dependencies 28 | 29 | i3status has the following dependencies: 30 | - libconfuse-dev 31 | - libyajl-dev 32 | - libasound2-dev 33 | - libiw-dev 34 | - libnotify 35 | - libmpdclient 36 | - libcap2-bin (for getting network status without root permissions) 37 | - asciidoc (only for the documentation) 38 | 39 | -------------------------------------------------------------------------------- /contrib/wrapper.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # vim:ts=4:sw=4:expandtab 3 | # © 2012 Michael Stapelberg, Public Domain 4 | 5 | # This script is a simple wrapper which prefixes each i3status line with custom 6 | # information. To use it, ensure your ~/.i3status.conf contains this line: 7 | # output_format = "i3bar" 8 | # in the 'general' section. 9 | # Then, in your ~/.i3/config, use: 10 | # status_command i3status | ~/i3status/contrib/wrapper.pl 11 | # In the 'bar' section. 12 | 13 | use strict; 14 | use warnings; 15 | # You can install the JSON module with 'cpan JSON' or by using your 16 | # distribution’s package management system, for example apt-get install 17 | # libjson-perl on Debian/Ubuntu. 18 | use JSON; 19 | 20 | # Don’t buffer any output. 21 | $| = 1; 22 | 23 | # Skip the first line which contains the version header. 24 | print scalar ; 25 | 26 | # The second line contains the start of the infinite array. 27 | print scalar ; 28 | 29 | # Read lines forever, ignore a comma at the beginning if it exists. 30 | while (my ($statusline) = ( =~ /^,?(.*)/)) { 31 | # Decode the JSON-encoded line. 32 | my @blocks = @{decode_json($statusline)}; 33 | 34 | # Prefix our own information (you could also suffix or insert in the 35 | # middle). 36 | @blocks = ({ 37 | full_text => 'MPD: not running', 38 | name => 'mpd' 39 | }, @blocks); 40 | 41 | # Output the line as JSON. 42 | print encode_json(\@blocks) . ",\n"; 43 | } 44 | -------------------------------------------------------------------------------- /src/print_time.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "i3status.h" 10 | 11 | static bool local_timezone_init = false; 12 | static const char *local_timezone = NULL; 13 | static const char *current_timezone = NULL; 14 | 15 | void set_timezone(const char *tz) { 16 | if (!local_timezone_init) { 17 | /* First call, initialize. */ 18 | local_timezone = getenv("TZ"); 19 | local_timezone_init = true; 20 | } 21 | if (tz == NULL || tz[0] == '\0') { 22 | /* User wants localtime. */ 23 | tz = local_timezone; 24 | } 25 | if (tz != current_timezone) { 26 | if (tz) { 27 | setenv("TZ", tz, 1); 28 | } else { 29 | unsetenv("TZ"); 30 | } 31 | tzset(); 32 | current_timezone = tz; 33 | } 34 | } 35 | 36 | void print_time(yajl_gen json_gen, char *buffer, const char *format, const char *tz, time_t t) { 37 | char *outwalk = buffer; 38 | struct tm tm; 39 | 40 | /* Convert time and format output. */ 41 | set_timezone(tz); 42 | localtime_r(&t, &tm); 43 | outwalk += strftime(outwalk, 4095, format, &tm); 44 | *outwalk = '\0'; 45 | OUTPUT_FULL_TEXT(buffer); 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | * Neither the name of i3status nor the names of contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/general.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "i3status.h" 12 | 13 | /* 14 | * Reads size bytes into the destination buffer from filename. 15 | * 16 | */ 17 | bool slurp(const char *filename, char *destination, int size) { 18 | int fd; 19 | 20 | if ((fd = open(filename, O_RDONLY)) == -1) 21 | return false; 22 | 23 | /* We need one byte for the trailing 0 byte */ 24 | int n = read(fd, destination, size-1); 25 | if (n != -1) 26 | destination[n] = '\0'; 27 | (void)close(fd); 28 | 29 | return true; 30 | } 31 | 32 | /* 33 | * Skip the given character for exactly 'amount' times, returns 34 | * a pointer to the first non-'character' character in 'input'. 35 | * 36 | */ 37 | char *skip_character(char *input, char character, int amount) { 38 | char *walk; 39 | size_t len = strlen(input); 40 | int blanks = 0; 41 | 42 | for (walk = input; ((size_t)(walk - input) < len) && (blanks < amount); walk++) 43 | if (*walk == character) 44 | blanks++; 45 | 46 | return (walk == input ? walk : walk-1); 47 | } 48 | 49 | /* 50 | * Write errormessage to statusbar and exit 51 | * 52 | */ 53 | void die(const char *fmt, ...) { 54 | char buffer[512]; 55 | va_list ap; 56 | va_start(ap, fmt); 57 | (void)vsnprintf(buffer, sizeof(buffer), fmt, ap); 58 | va_end(ap); 59 | 60 | fprintf(stderr, "%s", buffer); 61 | exit(EXIT_FAILURE); 62 | } 63 | -------------------------------------------------------------------------------- /src/print_ip_addr.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "i3status.h" 14 | 15 | /* 16 | * Return the IP address for the given interface or "no IP" if the 17 | * interface is up and running but hasn't got an IP address yet 18 | * 19 | */ 20 | const char *get_ip_addr(const char *interface) { 21 | static char part[512]; 22 | socklen_t len = sizeof(struct sockaddr_in); 23 | memset(part, 0, sizeof(part)); 24 | 25 | struct ifaddrs *ifaddr, *addrp; 26 | bool found = false; 27 | 28 | getifaddrs(&ifaddr); 29 | 30 | if (ifaddr == NULL) 31 | return NULL; 32 | 33 | /* Skip until we are at the AF_INET address of interface */ 34 | for (addrp = ifaddr; 35 | 36 | (addrp != NULL && 37 | (strcmp(addrp->ifa_name, interface) != 0 || 38 | addrp->ifa_addr == NULL || 39 | addrp->ifa_addr->sa_family != AF_INET)); 40 | 41 | addrp = addrp->ifa_next) { 42 | /* Check if the interface is down */ 43 | if (strcmp(addrp->ifa_name, interface) != 0) 44 | continue; 45 | found = true; 46 | if ((addrp->ifa_flags & IFF_RUNNING) == 0) { 47 | freeifaddrs(ifaddr); 48 | return NULL; 49 | } 50 | } 51 | 52 | if (addrp == NULL) { 53 | freeifaddrs(ifaddr); 54 | return (found ? "no IP" : NULL); 55 | } 56 | 57 | int ret; 58 | if ((ret = getnameinfo(addrp->ifa_addr, len, part, sizeof(part), NULL, 0, NI_NUMERICHOST)) != 0) { 59 | fprintf(stderr, "i3status: getnameinfo(): %s\n", gai_strerror(ret)); 60 | freeifaddrs(ifaddr); 61 | return "no IP"; 62 | } 63 | 64 | freeifaddrs(ifaddr); 65 | return part; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/print_load.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include "i3status.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void print_load(yajl_gen json_gen, char *buffer, const char *format, const float max_threshold) { 10 | char *outwalk = buffer; 11 | /* Get load */ 12 | 13 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(linux) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(sun) || defined(__DragonFly__) 14 | double loadavg[3]; 15 | const char *walk; 16 | bool colorful_output = false; 17 | 18 | if (getloadavg(loadavg, 3) == -1) 19 | goto error; 20 | 21 | for (walk = format; *walk != '\0'; walk++) { 22 | if (*walk != '%') { 23 | *(outwalk++) = *walk; 24 | continue; 25 | } 26 | if (loadavg[0] >= max_threshold) { 27 | START_COLOR("color_bad"); 28 | colorful_output = true; 29 | } 30 | 31 | if (BEGINS_WITH(walk+1, "1min")) { 32 | outwalk += sprintf(outwalk, "%1.2f", loadavg[0]); 33 | walk += strlen("1min"); 34 | } 35 | 36 | if (BEGINS_WITH(walk+1, "5min")) { 37 | outwalk += sprintf(outwalk, "%1.2f", loadavg[1]); 38 | walk += strlen("5min"); 39 | } 40 | 41 | if (BEGINS_WITH(walk+1, "15min")) { 42 | outwalk += sprintf(outwalk, "%1.2f", loadavg[2]); 43 | walk += strlen("15min"); 44 | } 45 | if (colorful_output) 46 | END_COLOR; 47 | } 48 | 49 | *outwalk = '\0'; 50 | OUTPUT_FULL_TEXT(buffer); 51 | 52 | return; 53 | error: 54 | #endif 55 | OUTPUT_FULL_TEXT("cant read load"); 56 | (void)fputs("i3status: Cannot read system load using getloadavg()\n", stderr); 57 | } 58 | -------------------------------------------------------------------------------- /contrib/measure-net-speed.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Public Domain 3 | # (someone claimed the next lines would be useful for… 4 | # people. So here goes: © 2012 Stefan Breunig 5 | # stefan+measure-net-speed@mathphys.fsk.uni-heidelberg.de) 6 | 7 | 8 | # path to store the old results in 9 | path="/dev/shm/measure-net-speed" 10 | 11 | # grabbing data for each adapter. 12 | # You can find the paths to your adapters using 13 | # find /sys/devices -name statistics 14 | # If you have more (or less) than two adapters, simply adjust the script here 15 | # and in the next block. 16 | eth0="/sys/devices/pci0000:00/0000:00:19.0/net/eth0/statistics" 17 | wlan0="/sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlan0/statistics" 18 | read eth0_rx < "${eth0}/rx_bytes" 19 | read eth0_tx < "${eth0}/tx_bytes" 20 | read wlan0_rx < "${wlan0}/rx_bytes" 21 | read wlan0_tx < "${wlan0}/tx_bytes" 22 | 23 | # get time and sum of rx/tx for combined display 24 | time=$(date +%s) 25 | rx=$(( $eth0_rx + $wlan0_rx )) 26 | tx=$(( $eth0_tx + $wlan0_tx )) 27 | 28 | # write current data if file does not exist. Do not exit, this will cause 29 | # problems if this file is sourced instead of executed as another process. 30 | if ! [[ -f "${path}" ]]; then 31 | echo "${time} ${rx} ${tx}" > "${path}" 32 | chmod 0666 "${path}" 33 | fi 34 | 35 | # read previous state and update data storage 36 | read old < "${path}" 37 | echo "${time} ${rx} ${tx}" > "${path}" 38 | 39 | # parse old data and calc time passed 40 | old=(${old//;/ }) 41 | time_diff=$(( $time - ${old[0]} )) 42 | 43 | # sanity check: has a positive amount of time passed 44 | if [[ "${time_diff}" -gt 0 ]]; then 45 | # calc bytes transferred, and their rate in byte/s 46 | rx_diff=$(( $rx - ${old[1]} )) 47 | tx_diff=$(( $tx - ${old[2]} )) 48 | rx_rate=$(( $rx_diff / $time_diff )) 49 | tx_rate=$(( $tx_diff / $time_diff )) 50 | 51 | # shift by 10 bytes to get KiB/s. If the value is larger than 52 | # 1024^2 = 1048576, then display MiB/s instead (simply cut off 53 | # the last two digits of KiB/s). Since the values only give an 54 | # rough estimate anyway, this improper rounding is negligible. 55 | 56 | # incoming 57 | rx_kib=$(( $rx_rate >> 10 )) 58 | if [[ "$rx_rate" -gt 1048576 ]]; then 59 | echo -n "${rx_kib:0:-3}.${rx_kib: -3:-2} M↓" 60 | else 61 | echo -n "${rx_kib} K↓" 62 | fi 63 | 64 | echo -n " " 65 | 66 | # outgoing 67 | tx_kib=$(( $tx_rate >> 10 )) 68 | if [[ "$tx_rate" -gt 1048576 ]]; then 69 | echo -n "${tx_kib:0:-3}.${tx_kib: -3:-2} M↑" 70 | else 71 | echo -n "${tx_kib} K↑" 72 | fi 73 | else 74 | echo -n " ? " 75 | fi -------------------------------------------------------------------------------- /contrib/wrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This script is a simple wrapper which prefixes each i3status line with custom 5 | # information. It is a python reimplementation of: 6 | # http://code.stapelberg.de/git/i3status/tree/contrib/wrapper.pl 7 | # 8 | # To use it, ensure your ~/.i3status.conf contains this line: 9 | # output_format = "i3bar" 10 | # in the 'general' section. 11 | # Then, in your ~/.i3/config, use: 12 | # status_command i3status | ~/i3status/contrib/wrapper.py 13 | # In the 'bar' section. 14 | # 15 | # In its current version it will display the cpu frequency governor, but you 16 | # are free to change it to display whatever you like, see the comment in the 17 | # source code below. 18 | # 19 | # © 2012 Valentin Haenel 20 | # 21 | # This program is free software. It comes without any warranty, to the extent 22 | # permitted by applicable law. You can redistribute it and/or modify it under 23 | # the terms of the Do What The Fuck You Want To Public License (WTFPL), Version 24 | # 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more 25 | # details. 26 | 27 | import sys 28 | import json 29 | 30 | def get_governor(): 31 | """ Get the current governor for cpu0, assuming all CPUs use the same. """ 32 | with open('/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor') as fp: 33 | return fp.readlines()[0].strip() 34 | 35 | def print_line(message): 36 | """ Non-buffered printing to stdout. """ 37 | sys.stdout.write(message + '\n') 38 | sys.stdout.flush() 39 | 40 | def read_line(): 41 | """ Interrupted respecting reader for stdin. """ 42 | # try reading a line, removing any extra whitespace 43 | try: 44 | line = sys.stdin.readline().strip() 45 | # i3status sends EOF, or an empty line 46 | if not line: 47 | sys.exit(3) 48 | return line 49 | # exit on ctrl-c 50 | except KeyboardInterrupt: 51 | sys.exit() 52 | 53 | if __name__ == '__main__': 54 | # Skip the first line which contains the version header. 55 | print_line(read_line()) 56 | 57 | # The second line contains the start of the infinite array. 58 | print_line(read_line()) 59 | 60 | while True: 61 | line, prefix = read_line(), '' 62 | # ignore comma at start of lines 63 | if line.startswith(','): 64 | line, prefix = line[1:], ',' 65 | 66 | j = json.loads(line) 67 | # insert information into the start of the json, but could be anywhere 68 | # CHANGE THIS LINE TO INSERT SOMETHING ELSE 69 | j.insert(0, {'full_text' : '%s' % get_governor(), 'name' : 'gov'}) 70 | # and echo back new encoded json 71 | print_line(prefix+json.dumps(j)) 72 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "i3status.h" 12 | 13 | /* 14 | * Returns the correct color format for dzen (^fg(color)) or xmobar () 15 | * 16 | */ 17 | char *color(const char *colorstr) { 18 | static char colorbuf[32]; 19 | if (!cfg_getbool(cfg_general, "colors")) { 20 | colorbuf[0] = '\0'; 21 | return colorbuf; 22 | } 23 | if (output_format == O_DZEN2) 24 | (void)snprintf(colorbuf, sizeof(colorbuf), "^fg(%s)", cfg_getstr(cfg_general, colorstr)); 25 | else if (output_format == O_XMOBAR) 26 | (void)snprintf(colorbuf, sizeof(colorbuf), "", cfg_getstr(cfg_general, colorstr)); 27 | else if (output_format == O_TERM) { 28 | /* The escape-sequence for color is ;1m (bright/bold 29 | * output), where col is a 3-bit rgb-value with b in the 30 | * least-significant bit. We round the given color to the 31 | * nearist 3-bit-depth color and output the escape-sequence */ 32 | char *str = cfg_getstr(cfg_general, colorstr); 33 | int col = strtol(str + 1, NULL, 16); 34 | int r = (col & (0xFF << 0)) / 0x80; 35 | int g = (col & (0xFF << 8)) / 0x8000; 36 | int b = (col & (0xFF << 16)) / 0x800000; 37 | col = (r << 2) | (g << 1) | b; 38 | (void)snprintf(colorbuf, sizeof(colorbuf), "\033[3%d;1m", col); 39 | } 40 | return colorbuf; 41 | } 42 | 43 | /* 44 | * Some color formats (xmobar) require to terminate colors again 45 | * 46 | */ 47 | char *endcolor(void) { 48 | if (output_format == O_XMOBAR) 49 | return ""; 50 | else if (output_format == O_TERM) 51 | return "\033[0m"; 52 | else return ""; 53 | } 54 | 55 | void print_seperator(void) { 56 | if (output_format == O_DZEN2) 57 | printf("^fg(%s)^p(5;-2)^ro(2)^p()^fg()^p(5)", cfg_getstr(cfg_general, "color_separator")); 58 | else if (output_format == O_XMOBAR) 59 | printf(" | ", cfg_getstr(cfg_general, "color_separator")); 60 | else if (output_format == O_TERM) 61 | printf(" %s|%s ", color("color_separator"), endcolor()); 62 | else if (output_format == O_NONE) 63 | printf(" | "); 64 | } 65 | 66 | /* 67 | * The term-output hides the cursor. We call this on exit to reset that. 68 | */ 69 | void reset_cursor(void) { 70 | printf("\033[?25h"); 71 | } 72 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef PREFIX 2 | PREFIX=/usr 3 | endif 4 | ifndef SYSCONFDIR 5 | ifeq ($(PREFIX),/usr) 6 | SYSCONFDIR=/etc 7 | else 8 | SYSCONFDIR=$(PREFIX)/etc 9 | endif 10 | endif 11 | 12 | CFLAGS+=-Wall -Wshadow -Wpointer-arith -Wcast-qual -Wsign-compare 13 | CFLAGS+=-g 14 | CFLAGS+=-std=gnu99 15 | CFLAGS+=-pedantic 16 | CPPFLAGS+=-DSYSCONFDIR=\"$(SYSCONFDIR)\" 17 | CPPFLAGS+=-DVERSION=\"${GIT_VERSION}\" 18 | CFLAGS+=-Iinclude \ 19 | $(shell pkg-config gtk+-2.0 --cflags) 20 | LIBS+=-lconfuse 21 | LIBS+=-lyajl 22 | LIBS+=-lmpdclient 23 | LIBS+=-lnotify \ 24 | $(shell pkg-config gtk+-2.0 --libs) 25 | 26 | VERSION:=$(shell git describe --tags --abbrev=0) 27 | GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" 28 | OS:=$(shell uname) 29 | 30 | ifeq ($(OS),Linux) 31 | CPPFLAGS+=-DLINUX 32 | CPPFLAGS+=-D_GNU_SOURCE 33 | LIBS+=-liw 34 | LIBS+=-lasound 35 | endif 36 | 37 | ifeq ($(OS),GNU/kFreeBSD) 38 | LIBS+=-lbsd 39 | endif 40 | 41 | ifeq ($(OS),OpenBSD) 42 | CFLAGS+=-I/usr/local/include/ 43 | LDFLAGS+=-L/usr/local/lib/ 44 | LIBS+=-lossaudio 45 | endif 46 | 47 | 48 | # This probably applies for any pkgsrc based system 49 | ifneq (, $(filter $(OS), NetBSD DragonFly)) 50 | CFLAGS+=-I/usr/pkg/include/ 51 | LDFLAGS+=-L/usr/pkg/lib/ 52 | endif 53 | 54 | ifeq ($(OS), NetBSD) 55 | LIBS+= -lprop 56 | endif 57 | 58 | 59 | V ?= 0 60 | ifeq ($(V),0) 61 | # Don’t print command lines which are run 62 | .SILENT: 63 | endif 64 | 65 | CFLAGS+=$(EXTRA_CFLAGS) 66 | 67 | # Fallback for libyajl 1 which did not include yajl_version.h. We need 68 | # YAJL_MAJOR from that file to decide which code path should be used. 69 | CFLAGS += -idirafter yajl-fallback 70 | 71 | OBJS:=$(wildcard src/*.c *.c) 72 | OBJS:=$(OBJS:.c=.o) 73 | 74 | src/%.o: src/%.c include/i3status.h 75 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 76 | @echo " CC $<" 77 | 78 | %.o: %.c include/%.h 79 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 80 | @echo " CC $<" 81 | 82 | all: i3status manpage 83 | 84 | i3status: ${OBJS} 85 | $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 86 | @echo " LD $@" 87 | 88 | clean: 89 | rm -f *.o src/*.o 90 | 91 | distclean: clean 92 | rm -f i3status man/i3status.1 93 | 94 | manpage: 95 | $(MAKE) -C man 96 | 97 | install: 98 | install -m 755 -d $(DESTDIR)$(PREFIX)/bin 99 | install -m 755 -d $(DESTDIR)$(SYSCONFDIR) 100 | install -m 755 -d $(DESTDIR)$(PREFIX)/share/man/man1 101 | install -m 755 i3status $(DESTDIR)$(PREFIX)/bin/i3status 102 | # Allow network configuration for getting the link speed 103 | (which setcap && setcap cap_net_admin=ep $(DESTDIR)$(PREFIX)/bin/i3status) || true 104 | install -m 644 i3status.conf $(DESTDIR)$(SYSCONFDIR)/i3status.conf 105 | install -m 644 man/i3status.1 $(DESTDIR)$(PREFIX)/share/man/man1 106 | 107 | release: 108 | [ -f i3status-${VERSION} ] || rm -rf i3status-${VERSION} 109 | mkdir i3status-${VERSION} 110 | find . -maxdepth 1 -type f \( -regex ".*\.\(c\|conf\|h\)" -or -name "Makefile" -or -name "LICENSE" -or -name "CHANGELOG" \) -exec cp '{}' i3status-${VERSION} \; 111 | mkdir i3status-${VERSION}/src 112 | mkdir i3status-${VERSION}/man 113 | find src -maxdepth 1 -type f \( -regex ".*\.\(c\|h\)" \) -exec cp '{}' i3status-${VERSION}/src \; 114 | find man -maxdepth 1 -type f \( -regex ".*\.\(1\|man\|conf\)" -or -name "Makefile" \) -exec cp '{}' i3status-${VERSION}/man \; 115 | cp -r include i3status-${VERSION} 116 | cp -r yajl-fallback i3status-${VERSION} 117 | cp -r contrib i3status-${VERSION} 118 | sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' Makefile > i3status-${VERSION}/Makefile 119 | tar cjf i3status-${VERSION}.tar.bz2 i3status-${VERSION} 120 | rm -rf i3status-${VERSION} 121 | -------------------------------------------------------------------------------- /src/print_cpu_usage.c: -------------------------------------------------------------------------------- 1 | // vim:sw=8:sts=8:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #if defined(__FreeBSD__) || defined(__OpenBSD__) 10 | #include 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | #if defined(__DragonFly__) 17 | #include 18 | #include 19 | #include 20 | #include 21 | #endif 22 | 23 | #if defined(__NetBSD__) 24 | #include 25 | #include 26 | #include 27 | #include 28 | #endif 29 | 30 | #include "i3status.h" 31 | 32 | static int prev_total = 0; 33 | static int prev_idle = 0; 34 | 35 | /* 36 | * Reads the CPU utilization from /proc/stat and returns the usage as a 37 | * percentage. 38 | * 39 | */ 40 | void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format) { 41 | const char *walk; 42 | char *outwalk = buffer; 43 | char buf[1024]; 44 | int curr_user = 0, curr_nice = 0, curr_system = 0, curr_idle = 0, curr_total; 45 | int diff_idle, diff_total, diff_usage; 46 | 47 | #if defined(LINUX) 48 | static char statpath[512]; 49 | strcpy(statpath, "/proc/stat"); 50 | if (!slurp(statpath, buf, sizeof(buf)) || 51 | sscanf(buf, "cpu %d %d %d %d", &curr_user, &curr_nice, &curr_system, &curr_idle) != 4) 52 | goto error; 53 | 54 | curr_total = curr_user + curr_nice + curr_system + curr_idle; 55 | diff_idle = curr_idle - prev_idle; 56 | diff_total = curr_total - prev_total; 57 | diff_usage = (diff_total ? (1000 * (diff_total - diff_idle)/diff_total + 5)/10 : 0); 58 | prev_total = curr_total; 59 | prev_idle = curr_idle; 60 | #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) 61 | 62 | #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) 63 | size_t size; 64 | long cp_time[CPUSTATES]; 65 | size = sizeof cp_time; 66 | if (sysctlbyname("kern.cp_time", &cp_time, &size, NULL, 0) < 0) 67 | goto error; 68 | #else 69 | /* This information is taken from the boot cpu, any other cpus are currently ignored. */ 70 | long cp_time[CPUSTATES]; 71 | int mib[2]; 72 | size_t size = sizeof(cp_time); 73 | 74 | mib[0] = CTL_KERN; 75 | mib[1] = KERN_CPTIME; 76 | 77 | if (sysctl(mib, 2, cp_time, &size, NULL, 0)) 78 | goto error; 79 | #endif 80 | 81 | curr_user = cp_time[CP_USER]; 82 | curr_nice = cp_time[CP_NICE]; 83 | curr_system = cp_time[CP_SYS]; 84 | curr_idle = cp_time[CP_IDLE]; 85 | curr_total = curr_user + curr_nice + curr_system + curr_idle; 86 | diff_idle = curr_idle - prev_idle; 87 | diff_total = curr_total - prev_total; 88 | diff_usage = (diff_total ? (1000 * (diff_total - diff_idle)/diff_total + 5)/10 : 0); 89 | prev_total = curr_total; 90 | prev_idle = curr_idle; 91 | #else 92 | goto error; 93 | #endif 94 | for (walk = format; *walk != '\0'; walk++) { 95 | if (*walk != '%') { 96 | *(outwalk++) = *walk; 97 | continue; 98 | } 99 | 100 | if (strncmp(walk+1, "usage", strlen("usage")) == 0) { 101 | outwalk += sprintf(outwalk, "%02d%%", diff_usage); 102 | walk += strlen("usage"); 103 | } 104 | } 105 | 106 | OUTPUT_FULL_TEXT(buffer); 107 | return; 108 | error: 109 | OUTPUT_FULL_TEXT("cant read cpu usage"); 110 | (void)fputs("i3status: Cannot read CPU usage\n", stderr); 111 | } 112 | -------------------------------------------------------------------------------- /src/print_disk_info.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || (__OpenBSD__) || defined(__DragonFly__) 10 | #include 11 | #include 12 | #endif 13 | #include 14 | #include 15 | 16 | #include "i3status.h" 17 | 18 | #define BINARY_BASE UINT64_C(1024) 19 | #define DECIMAL_BASE UINT64_C(1000) 20 | 21 | #define MAX_EXPONENT 4 22 | 23 | static const char * const iec_symbols[MAX_EXPONENT+1] = {"", "Ki", "Mi", "Gi", "Ti"}; 24 | static const char * const si_symbols[MAX_EXPONENT+1] = {"", "k", "M", "G", "T"}; 25 | static const char * const custom_symbols[MAX_EXPONENT+1] = {"", "K", "M", "G", "T"}; 26 | 27 | /* 28 | * Formats bytes according to the given base and set of symbols. 29 | * 30 | */ 31 | static int format_bytes(char *outwalk, uint64_t bytes, uint64_t base, const char * const symbols[]) { 32 | double size = bytes; 33 | int exponent = 0; 34 | while (size >= base && exponent < MAX_EXPONENT) { 35 | size /= base; 36 | exponent += 1; 37 | } 38 | return sprintf(outwalk, "%.1f %sB", size, symbols[exponent]); 39 | } 40 | 41 | /* 42 | * Prints the given amount of bytes in a human readable manner. 43 | * 44 | */ 45 | static int print_bytes_human(char *outwalk, uint64_t bytes, const char *prefix_type) { 46 | if (strncmp(prefix_type, "decimal", strlen(prefix_type)) == 0) { 47 | return format_bytes(outwalk, bytes, DECIMAL_BASE, si_symbols); 48 | } else if (strncmp(prefix_type, "custom", strlen(prefix_type)) == 0) { 49 | return format_bytes(outwalk, bytes, BINARY_BASE, custom_symbols); 50 | } else { 51 | return format_bytes(outwalk, bytes, BINARY_BASE, iec_symbols); 52 | } 53 | } 54 | 55 | /* 56 | * Does a statvfs and prints either free, used or total amounts of bytes in a 57 | * human readable manner. 58 | * 59 | */ 60 | void print_disk_info(yajl_gen json_gen, char *buffer, const char *path, const char *format, const char *prefix_type) { 61 | const char *walk; 62 | char *outwalk = buffer; 63 | 64 | INSTANCE(path); 65 | 66 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__DragonFly__) 67 | struct statfs buf; 68 | 69 | if (statfs(path, &buf) == -1) 70 | return; 71 | #else 72 | struct statvfs buf; 73 | 74 | if (statvfs(path, &buf) == -1) 75 | return; 76 | #endif 77 | 78 | for (walk = format; *walk != '\0'; walk++) { 79 | if (*walk != '%') { 80 | *(outwalk++) = *walk; 81 | continue; 82 | } 83 | 84 | if (BEGINS_WITH(walk+1, "free")) { 85 | outwalk += print_bytes_human(outwalk, (uint64_t)buf.f_bsize * (uint64_t)buf.f_bfree, prefix_type); 86 | walk += strlen("free"); 87 | } 88 | 89 | if (BEGINS_WITH(walk+1, "used")) { 90 | outwalk += print_bytes_human(outwalk, (uint64_t)buf.f_bsize * ((uint64_t)buf.f_blocks - (uint64_t)buf.f_bfree), prefix_type); 91 | walk += strlen("used"); 92 | } 93 | 94 | if (BEGINS_WITH(walk+1, "total")) { 95 | outwalk += print_bytes_human(outwalk, (uint64_t)buf.f_bsize * (uint64_t)buf.f_blocks, prefix_type); 96 | walk += strlen("total"); 97 | } 98 | 99 | if (BEGINS_WITH(walk+1, "avail")) { 100 | outwalk += print_bytes_human(outwalk, (uint64_t)buf.f_bsize * (uint64_t)buf.f_bavail, prefix_type); 101 | walk += strlen("avail"); 102 | } 103 | 104 | if (BEGINS_WITH(walk+1, "percentage_free")) { 105 | outwalk += sprintf(outwalk, "%.01f%%", 100.0 * (double)buf.f_bfree / (double)buf.f_blocks); 106 | walk += strlen("percentage_free"); 107 | } 108 | 109 | if (BEGINS_WITH(walk+1, "percentage_used_of_avail")) { 110 | outwalk += sprintf(outwalk, "%.01f%%", 100.0 * (double)(buf.f_blocks - buf.f_bavail) / (double)buf.f_blocks); 111 | walk += strlen("percentage_used_of_avail"); 112 | } 113 | 114 | if (BEGINS_WITH(walk+1, "percentage_used")) { 115 | outwalk += sprintf(outwalk, "%.01f%%", 100.0 * (double)(buf.f_blocks - buf.f_bfree) / (double)buf.f_blocks); 116 | walk += strlen("percentage_used"); 117 | } 118 | 119 | if (BEGINS_WITH(walk+1, "percentage_avail")) { 120 | outwalk += sprintf(outwalk, "%.01f%%", 100.0 * (double)buf.f_bavail / (double)buf.f_blocks); 121 | walk += strlen("percentage_avail"); 122 | } 123 | } 124 | 125 | *outwalk = '\0'; 126 | OUTPUT_FULL_TEXT(buffer); 127 | } 128 | -------------------------------------------------------------------------------- /src/print_eth_info.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "i3status.h" 15 | 16 | #if defined(LINUX) 17 | #include 18 | #include 19 | #define PART_ETHSPEED "E: %s (%d Mbit/s)" 20 | #endif 21 | 22 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 23 | #include 24 | #define IFM_TYPE_MATCH(dt, t) \ 25 | (IFM_TYPE((dt)) == 0 || IFM_TYPE((dt)) == IFM_TYPE((t))) 26 | 27 | #define PART_ETHSPEED "E: %s (%s)" 28 | #endif 29 | 30 | #if defined(__OpenBSD__) || defined(__NetBSD__) 31 | #include 32 | #include 33 | #endif 34 | 35 | static int print_eth_speed(char *outwalk, const char *interface) { 36 | #if defined(LINUX) 37 | /* This code path requires root privileges */ 38 | int ethspeed = 0; 39 | struct ifreq ifr; 40 | struct ethtool_cmd ecmd; 41 | 42 | ecmd.cmd = ETHTOOL_GSET; 43 | (void)memset(&ifr, 0, sizeof(ifr)); 44 | ifr.ifr_data = (caddr_t)&ecmd; 45 | (void)strcpy(ifr.ifr_name, interface); 46 | if (ioctl(general_socket, SIOCETHTOOL, &ifr) == 0) { 47 | ethspeed = (ecmd.speed == USHRT_MAX ? 0 : ecmd.speed); 48 | return sprintf(outwalk, "%d Mbit/s", ethspeed); 49 | } else return sprintf(outwalk, "?"); 50 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 51 | char *ethspeed; 52 | struct ifmediareq ifm; 53 | (void)memset(&ifm, 0, sizeof(ifm)); 54 | (void)strncpy(ifm.ifm_name, interface, sizeof(ifm.ifm_name)); 55 | int ret = ioctl(general_socket, SIOCGIFMEDIA, (caddr_t)&ifm); 56 | 57 | /* Get the description of the media type, partially taken from 58 | * FreeBSD's ifconfig */ 59 | const struct ifmedia_description *desc; 60 | struct ifmedia_description ifm_subtype_descriptions[] = 61 | IFM_SUBTYPE_ETHERNET_DESCRIPTIONS; 62 | 63 | for (desc = ifm_subtype_descriptions; 64 | desc->ifmt_string != NULL; 65 | desc++) { 66 | if (IFM_TYPE_MATCH(desc->ifmt_word, ifm.ifm_active) && 67 | IFM_SUBTYPE(desc->ifmt_word) == IFM_SUBTYPE(ifm.ifm_active)) 68 | break; 69 | } 70 | ethspeed = (desc->ifmt_string != NULL ? desc->ifmt_string : "?"); 71 | return sprintf(outwalk, "%s", ethspeed); 72 | #elif defined(__OpenBSD__) || defined(__NetBSD__) 73 | char *ethspeed; 74 | struct ifmediareq ifmr; 75 | 76 | (void) memset(&ifmr, 0, sizeof(ifmr)); 77 | (void) strlcpy(ifmr.ifm_name, interface, sizeof(ifmr.ifm_name)); 78 | 79 | if (ioctl(general_socket, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) { 80 | if (errno != E2BIG) 81 | return sprintf(outwalk, "?"); 82 | } 83 | 84 | struct ifmedia_description *desc; 85 | struct ifmedia_description ifm_subtype_descriptions[] = 86 | IFM_SUBTYPE_DESCRIPTIONS; 87 | 88 | for (desc = ifm_subtype_descriptions; desc->ifmt_string != NULL; desc++) { 89 | /* 90 | * Skip these non-informative values and go right ahead to the 91 | * actual speeds. 92 | */ 93 | if (strncmp(desc->ifmt_string, "autoselect", strlen("autoselect")) == 0 || 94 | strncmp(desc->ifmt_string, "auto", strlen("auto")) == 0) 95 | continue; 96 | 97 | if (IFM_TYPE_MATCH(desc->ifmt_word, ifmr.ifm_active) && 98 | IFM_SUBTYPE(desc->ifmt_word) == IFM_SUBTYPE(ifmr.ifm_active)) 99 | break; 100 | } 101 | ethspeed = (desc->ifmt_string != NULL ? desc->ifmt_string : "?"); 102 | return sprintf(outwalk, "%s", ethspeed); 103 | 104 | #else 105 | return sprintf(outwalk, "?"); 106 | #endif 107 | } 108 | 109 | /* 110 | * Combines ethernet IP addresses and speed (if requested) for displaying 111 | * 112 | */ 113 | void print_eth_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down) { 114 | const char *walk; 115 | const char *ip_address = get_ip_addr(interface); 116 | char *outwalk = buffer; 117 | 118 | INSTANCE(interface); 119 | 120 | if (ip_address == NULL) { 121 | START_COLOR("color_bad"); 122 | outwalk += sprintf(outwalk, "%s", format_down); 123 | goto out; 124 | } 125 | 126 | START_COLOR("color_good"); 127 | 128 | for (walk = format_up; *walk != '\0'; walk++) { 129 | if (*walk != '%') { 130 | *(outwalk++) = *walk; 131 | continue; 132 | } 133 | 134 | if (strncmp(walk+1, "ip", strlen("ip")) == 0) { 135 | outwalk += sprintf(outwalk, "%s", ip_address); 136 | walk += strlen("ip"); 137 | } else if (strncmp(walk+1, "speed", strlen("speed")) == 0) { 138 | outwalk += print_eth_speed(outwalk, interface); 139 | walk += strlen("speed"); 140 | } 141 | } 142 | 143 | out: 144 | END_COLOR; 145 | OUTPUT_FULL_TEXT(buffer); 146 | } 147 | -------------------------------------------------------------------------------- /src/auto_detect_format.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "i3status.h" 16 | 17 | /* 18 | * Reads /proc//stat and returns (via pointers) the name and parent pid of 19 | * the specified pid. 20 | * When false is returned, parsing failed and the contents of outname and 21 | * outpid are undefined. 22 | * 23 | */ 24 | static bool parse_proc_stat(pid_t pid, char **outname, pid_t *outppid) { 25 | char path[255]; 26 | /* the relevant contents (for us) are: 27 | * () 28 | * which should well fit into one page of 4096 bytes */ 29 | char buffer[4096]; 30 | 31 | if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 || 32 | !slurp(path, buffer, sizeof(buffer))) 33 | return false; 34 | 35 | char *leftbracket = strchr(buffer, '('); 36 | char *rightbracket = strrchr(buffer, ')'); 37 | if (!leftbracket || 38 | !rightbracket || 39 | sscanf(rightbracket + 2, "%*c %d", outppid) != 1) 40 | return false; 41 | *rightbracket = '\0'; 42 | *outname = strdup(leftbracket + 1); 43 | return true; 44 | } 45 | 46 | static char *format_for_process(const char *name) { 47 | if (strcasecmp(name, "i3bar") == 0) 48 | return "i3bar"; 49 | else if (strcasecmp(name, "dzen2") == 0) 50 | return "dzen2"; 51 | else if (strcasecmp(name, "xmobar") == 0) 52 | return "xmobar"; 53 | else 54 | return NULL; 55 | } 56 | 57 | /* 58 | * This function tries to automatically find out where i3status is being piped 59 | * to and choses the appropriate output format. 60 | * 61 | * It is a little hackish but should work for most setups :). 62 | * 63 | * By iterating through /proc//stat and finding out the parent process 64 | * id (just like pstree(1) or ps(1) work), we can get all children of our 65 | * parent. When the output of i3status is being piped somewhere, the shell 66 | * (parent process) spawns i3status and the destination process, so we will 67 | * find our own process and the pipe target. 68 | * 69 | * We then check whether the pipe target’s name is known and chose the format. 70 | * 71 | */ 72 | char *auto_detect_format(void) { 73 | /* If stdout is a tty, we output directly to a terminal. */ 74 | if (isatty(STDOUT_FILENO)) { 75 | return "term"; 76 | } 77 | 78 | pid_t myppid = getppid(); 79 | pid_t mypid = getpid(); 80 | 81 | DIR *dir; 82 | struct dirent *entry; 83 | 84 | char *format = NULL; 85 | 86 | char *parentname; 87 | pid_t parentpid; 88 | 89 | if (!parse_proc_stat(myppid, &parentname, &parentpid)) 90 | return NULL; 91 | 92 | if (strcmp(parentname, "sh") == 0) { 93 | pid_t tmp_ppid = parentpid; 94 | free(parentname); 95 | fprintf(stderr, "i3status: auto-detection: parent process is \"sh\", looking at its parent\n"); 96 | if (!parse_proc_stat(tmp_ppid, &parentname, &parentpid)) 97 | return NULL; 98 | } 99 | 100 | /* Some shells, for example zsh, open a pipe in a way which will make the 101 | * pipe target the parent process of i3status. If we detect that, we set 102 | * the format and we are done. */ 103 | if ((format = format_for_process(parentname)) != NULL) 104 | goto out; 105 | 106 | if (!(dir = opendir("/proc"))) 107 | goto out; 108 | 109 | while ((entry = readdir(dir)) != NULL) { 110 | pid_t pid = (pid_t)atoi(entry->d_name); 111 | if (pid == 0 || pid == mypid) 112 | continue; 113 | 114 | char *name = NULL; 115 | pid_t ppid; 116 | int loopcnt = 0; 117 | /* Now we need to find out the name of the process. 118 | * To avoid the possible race condition of the process existing already 119 | * but not executing the destination (shell after fork() and before 120 | * exec()), we check if the name equals its parent. 121 | * 122 | * We try this for up to 0.5 seconds, then we give up. 123 | */ 124 | do { 125 | /* give the scheduler a chance between each iteration, don’t hog 126 | * the CPU too much */ 127 | if (name) { 128 | usleep(50); 129 | free(name); 130 | } 131 | 132 | if (!parse_proc_stat(pid, &name, &ppid)) 133 | break; 134 | if (ppid != myppid) 135 | break; 136 | } while (strcmp(parentname, name) == 0 && loopcnt++ < 10000); 137 | 138 | if (!name) 139 | continue; 140 | 141 | /* Check for known destination programs and set format */ 142 | char *newfmt = format_for_process(name); 143 | free(name); 144 | 145 | if (newfmt && format && strcmp(newfmt, format) != 0) { 146 | fprintf(stderr, "i3status: cannot auto-configure, situation ambiguous (format \"%s\" *and* \"%s\" detected)\n", newfmt, format); 147 | format = NULL; 148 | break; 149 | } else { 150 | format = newfmt; 151 | } 152 | } 153 | 154 | closedir(dir); 155 | 156 | out: 157 | if (parentname) 158 | free(parentname); 159 | 160 | return format; 161 | } 162 | -------------------------------------------------------------------------------- /src/print_volume.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef LINUX 10 | #include 11 | #include 12 | #endif 13 | 14 | #if defined(__FreeBSD__) || defined(__DragonFly__) 15 | #include 16 | #include 17 | #include 18 | #endif 19 | 20 | #ifdef __OpenBSD__ 21 | #include 22 | #include 23 | #include 24 | #endif 25 | 26 | #include "i3status.h" 27 | #include "queue.h" 28 | 29 | 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) { 30 | char *outwalk = buffer; 31 | int pbval = 1; 32 | 33 | /* Printing volume only works with ALSA at the moment */ 34 | if (output_format == O_I3BAR) { 35 | char *instance; 36 | asprintf(&instance, "%s.%s.%d", device, mixer, mixer_idx); 37 | INSTANCE(instance); 38 | free(instance); 39 | } 40 | #ifdef LINUX 41 | int err; 42 | snd_mixer_t *m; 43 | snd_mixer_selem_id_t *sid; 44 | snd_mixer_elem_t *elem; 45 | long min, max, val; 46 | int avg; 47 | 48 | if ((err = snd_mixer_open(&m, 0)) < 0) { 49 | fprintf(stderr, "i3status: ALSA: Cannot open mixer: %s\n", snd_strerror(err)); 50 | goto out; 51 | } 52 | 53 | /* Attach this mixer handle to the given device */ 54 | if ((err = snd_mixer_attach(m, device)) < 0) { 55 | fprintf(stderr, "i3status: ALSA: Cannot attach mixer to device: %s\n", snd_strerror(err)); 56 | snd_mixer_close(m); 57 | goto out; 58 | } 59 | 60 | /* Register this mixer */ 61 | if ((err = snd_mixer_selem_register(m, NULL, NULL)) < 0) { 62 | fprintf(stderr, "i3status: ALSA: snd_mixer_selem_register: %s\n", snd_strerror(err)); 63 | snd_mixer_close(m); 64 | goto out; 65 | } 66 | 67 | if ((err = snd_mixer_load(m)) < 0) { 68 | fprintf(stderr, "i3status: ALSA: snd_mixer_load: %s\n", snd_strerror(err)); 69 | snd_mixer_close(m); 70 | goto out; 71 | } 72 | 73 | snd_mixer_selem_id_malloc(&sid); 74 | if (sid == NULL) { 75 | snd_mixer_close(m); 76 | goto out; 77 | } 78 | 79 | /* Find the given mixer */ 80 | snd_mixer_selem_id_set_index(sid, mixer_idx); 81 | snd_mixer_selem_id_set_name(sid, mixer); 82 | if (!(elem = snd_mixer_find_selem(m, sid))) { 83 | fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n", 84 | snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); 85 | snd_mixer_close(m); 86 | snd_mixer_selem_id_free(sid); 87 | goto out; 88 | } 89 | 90 | /* Get the volume range to convert the volume later */ 91 | snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 92 | 93 | snd_mixer_handle_events (m); 94 | snd_mixer_selem_get_playback_volume (elem, 0, &val); 95 | if (max != 100) { 96 | float avgf = ((float)val / max) * 100; 97 | avg = (int)avgf; 98 | avg = (avgf - avg < 0.5 ? avg : (avg+1)); 99 | } else avg = (int)val; 100 | 101 | /* Check for mute */ 102 | if (snd_mixer_selem_has_playback_switch(elem)) { 103 | if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0) 104 | fprintf (stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err)); 105 | if (!pbval) { 106 | START_COLOR("color_degraded"); 107 | fmt = fmt_muted; 108 | } 109 | } 110 | 111 | snd_mixer_close(m); 112 | snd_mixer_selem_id_free(sid); 113 | 114 | const char *walk = fmt; 115 | for (; *walk != '\0'; walk++) { 116 | if (*walk != '%') { 117 | *(outwalk++) = *walk; 118 | continue; 119 | } 120 | if (BEGINS_WITH(walk+1, "%")) { 121 | outwalk += sprintf(outwalk, "%%"); 122 | walk += strlen("%"); 123 | } 124 | if (BEGINS_WITH(walk+1, "volume")) { 125 | outwalk += sprintf(outwalk, "%d%%", avg); 126 | walk += strlen("volume"); 127 | } 128 | } 129 | #endif 130 | #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) 131 | char *mixerpath; 132 | char defaultmixer[] = "/dev/mixer"; 133 | int mixfd, vol, devmask = 0; 134 | pbval = 1; 135 | 136 | if (mixer_idx > 0) 137 | asprintf(&mixerpath, "/dev/mixer%d", mixer_idx); 138 | else 139 | mixerpath = defaultmixer; 140 | 141 | if ((mixfd = open(mixerpath, O_RDWR)) < 0) 142 | return; 143 | 144 | if (mixer_idx > 0) 145 | free(mixerpath); 146 | 147 | if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) 148 | return; 149 | if (ioctl(mixfd, MIXER_READ(0),&vol) == -1) 150 | return; 151 | 152 | if (((vol & 0x7f) == 0) && (((vol >> 8) & 0x7f) == 0)) { 153 | START_COLOR("color_degraded"); 154 | pbval = 0; 155 | } 156 | 157 | const char *walk = fmt; 158 | for (; *walk != '\0'; walk++) { 159 | if (*walk != '%') { 160 | *(outwalk++) = *walk; 161 | continue; 162 | } 163 | if (BEGINS_WITH(walk+1, "%")) { 164 | outwalk += sprintf(outwalk, "%%"); 165 | walk += strlen("%"); 166 | } 167 | if (BEGINS_WITH(walk+1, "volume")) { 168 | outwalk += sprintf(outwalk, "%d%%", vol & 0x7f); 169 | walk += strlen("volume"); 170 | } 171 | } 172 | close(mixfd); 173 | #endif 174 | 175 | out: 176 | *outwalk = '\0'; 177 | OUTPUT_FULL_TEXT(buffer); 178 | 179 | if (!pbval) 180 | END_COLOR; 181 | } 182 | -------------------------------------------------------------------------------- /src/print_mpd.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "i3status.h" 11 | 12 | #define MPD_OUTPUT_OPTION(option_name, tag_name) \ 13 | if (BEGINS_WITH(walk + 1, option_name)) { \ 14 | if ((value = mpd_song_get_tag(song, tag_name, 0)) == NULL) { \ 15 | *(outwalk++) = '?'; \ 16 | } else { \ 17 | outwalk += sprintf(outwalk, "%s", value); \ 18 | } \ 19 | walk += strlen(option_name); \ 20 | } 21 | 22 | static char *prev_song; 23 | static struct mpd_connection *conn = NULL; 24 | 25 | void mpd_format_string( 26 | struct mpd_song *song, 27 | const char *format, 28 | char *str, 29 | char **output 30 | ) { 31 | char *outwalk = *output; 32 | assert(outwalk == str); 33 | 34 | const char *walk; 35 | const char *value; 36 | 37 | for (walk = format; *walk != '\0'; walk++) { 38 | if (*walk != '%') { 39 | *(outwalk++) = *walk; 40 | continue; 41 | } 42 | 43 | MPD_OUTPUT_OPTION("artist", MPD_TAG_ARTIST) 44 | MPD_OUTPUT_OPTION("album_artist", MPD_TAG_ALBUM_ARTIST) 45 | else MPD_OUTPUT_OPTION("album", MPD_TAG_ALBUM) 46 | MPD_OUTPUT_OPTION("title", MPD_TAG_TITLE) 47 | MPD_OUTPUT_OPTION("track", MPD_TAG_TRACK) 48 | MPD_OUTPUT_OPTION("name", MPD_TAG_NAME) 49 | MPD_OUTPUT_OPTION("genre", MPD_TAG_GENRE) 50 | MPD_OUTPUT_OPTION("date", MPD_TAG_DATE) 51 | MPD_OUTPUT_OPTION("composer", MPD_TAG_COMPOSER) 52 | MPD_OUTPUT_OPTION("performer", MPD_TAG_PERFORMER) 53 | MPD_OUTPUT_OPTION("comment", MPD_TAG_COMMENT) 54 | MPD_OUTPUT_OPTION("disc", MPD_TAG_DISC) 55 | } 56 | 57 | } 58 | 59 | void mpd_send_notification( 60 | struct mpd_song *song, 61 | const char *header_format, 62 | const char *body_format 63 | ) { 64 | char *outwalk; 65 | 66 | char header[4096]; 67 | outwalk = header; 68 | mpd_format_string(song, header_format, header, &outwalk); 69 | 70 | char body[4096]; 71 | outwalk = body; 72 | mpd_format_string(song, body_format, body, &outwalk); 73 | 74 | NotifyNotification *song_notif = notify_notification_new(header, body, "dialog-information"); 75 | notify_notification_show(song_notif, NULL); 76 | g_object_unref(G_OBJECT(song_notif)); 77 | } 78 | 79 | void print_mpd( 80 | yajl_gen json_gen, 81 | char *buffer, 82 | const char *format, 83 | const char *format_stopped, 84 | const char *notif_header_format, 85 | const char *notif_body_format 86 | ) { 87 | char *outwalk = buffer; 88 | 89 | struct mpd_song *song; 90 | 91 | /* Use defaults */ 92 | if (conn == NULL) { 93 | if ((conn = mpd_connection_new(NULL, 0, 0)) == NULL) { 94 | outwalk += sprintf(outwalk, "%s", format_stopped); 95 | OUTPUT_FULL_TEXT(buffer); 96 | return; 97 | } 98 | } 99 | 100 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 101 | mpd_connection_free(conn); 102 | conn = NULL; 103 | outwalk += sprintf(outwalk, "%s", format_stopped); 104 | OUTPUT_FULL_TEXT(buffer); 105 | return; 106 | } 107 | 108 | /* Get current song */ 109 | song = mpd_run_current_song(conn); 110 | if (song == NULL) { 111 | outwalk += sprintf(outwalk, "%s", format_stopped); 112 | OUTPUT_FULL_TEXT(buffer); 113 | return; 114 | } 115 | 116 | const char *walk; 117 | const char *value; 118 | 119 | for (walk = format; *walk != '\0'; walk++) { 120 | if (*walk != '%') { 121 | *(outwalk++) = *walk; 122 | continue; 123 | } 124 | 125 | MPD_OUTPUT_OPTION("artist", MPD_TAG_ARTIST) 126 | MPD_OUTPUT_OPTION("album_artist", MPD_TAG_ALBUM_ARTIST) 127 | else MPD_OUTPUT_OPTION("album", MPD_TAG_ALBUM) 128 | MPD_OUTPUT_OPTION("title", MPD_TAG_TITLE) 129 | MPD_OUTPUT_OPTION("track", MPD_TAG_TRACK) 130 | MPD_OUTPUT_OPTION("name", MPD_TAG_NAME) 131 | MPD_OUTPUT_OPTION("genre", MPD_TAG_GENRE) 132 | MPD_OUTPUT_OPTION("date", MPD_TAG_DATE) 133 | MPD_OUTPUT_OPTION("composer", MPD_TAG_COMPOSER) 134 | MPD_OUTPUT_OPTION("performer", MPD_TAG_PERFORMER) 135 | MPD_OUTPUT_OPTION("comment", MPD_TAG_COMMENT) 136 | MPD_OUTPUT_OPTION("disc", MPD_TAG_DISC) 137 | } 138 | 139 | 140 | const char *uri = mpd_song_get_uri(song); 141 | 142 | // Determine if this is a different song than the last time we checked 143 | if (prev_song != NULL && strcmp(prev_song, uri) == 0) 144 | goto out; 145 | 146 | // Otherwise, update the text and send a notification 147 | mpd_send_notification(song, notif_header_format, notif_body_format); 148 | 149 | // Copy the new URI into the static prev_song string 150 | free(prev_song); 151 | if ((prev_song = (char *)malloc(sizeof(char) * (strlen(uri) + 1))) == NULL) 152 | goto out; 153 | strcpy(prev_song, uri); 154 | 155 | out: 156 | mpd_song_free(song); 157 | OUTPUT_FULL_TEXT(buffer); 158 | return; 159 | } 160 | 161 | 162 | void cleanup_mpd() { 163 | if (conn != NULL) { 164 | mpd_connection_free(conn); 165 | } 166 | } 167 | 168 | -------------------------------------------------------------------------------- /src/print_ipv6_addr.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "i3status.h" 17 | 18 | static char *get_sockname(struct addrinfo *addr) { 19 | static char buf[INET6_ADDRSTRLEN+1]; 20 | struct sockaddr_storage local; 21 | int ret; 22 | int fd; 23 | 24 | if ((fd = socket(addr->ai_family, SOCK_DGRAM, 0)) == -1) { 25 | perror("socket()"); 26 | return NULL; 27 | } 28 | 29 | /* Since the socket was created with SOCK_DGRAM, this is 30 | * actually not establishing a connection or generating 31 | * any other network traffic. Instead, as a side-effect, 32 | * it saves the local address with which packets would 33 | * be sent to the destination. */ 34 | if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) { 35 | /* We don’t display the error here because most 36 | * likely, there just is no IPv6 connectivity. 37 | * Thus, don’t spam the user’s console but just 38 | * try the next address. */ 39 | (void)close(fd); 40 | return NULL; 41 | } 42 | 43 | socklen_t local_len = sizeof(struct sockaddr_storage); 44 | if (getsockname(fd, (struct sockaddr*)&local, &local_len) == -1) { 45 | perror("getsockname()"); 46 | (void)close(fd); 47 | return NULL; 48 | } 49 | 50 | memset(buf, 0, INET6_ADDRSTRLEN + 1); 51 | if ((ret = getnameinfo((struct sockaddr*)&local, local_len, 52 | buf, sizeof(buf), NULL, 0, 53 | NI_NUMERICHOST)) != 0) { 54 | fprintf(stderr, "i3status: getnameinfo(): %s\n", gai_strerror(ret)); 55 | (void)close(fd); 56 | return NULL; 57 | } 58 | 59 | (void)close(fd); 60 | return buf; 61 | } 62 | 63 | /* 64 | * Returns the IPv6 address with which you have connectivity at the moment. 65 | * The char * is statically allocated and mustn't be freed 66 | */ 67 | static char *get_ipv6_addr(void) { 68 | struct addrinfo hints; 69 | struct addrinfo *result, *resp; 70 | static struct addrinfo *cached = NULL; 71 | 72 | /* To save dns lookups (if they are not cached locally) and creating 73 | * sockets, we save the fd and keep it open. */ 74 | if (cached != NULL) 75 | return get_sockname(cached); 76 | 77 | memset(&hints, 0, sizeof(struct addrinfo)); 78 | hints.ai_family = AF_INET6; 79 | hints.ai_socktype = SOCK_DGRAM; 80 | 81 | /* We use the public IPv6 of the K root server here. It doesn’t matter 82 | * which IPv6 address we use (we don’t even send any packets), as long 83 | * as it’s considered global by the kernel. 84 | * NB: We don’t use a hostname since that would trigger a DNS lookup. 85 | * By using an IPv6 address, getaddrinfo() will *not* do a DNS lookup, 86 | * but return the address in the appropriate struct. */ 87 | if (getaddrinfo("2001:7fd::1", "domain", &hints, &result) != 0) { 88 | /* We don’t display the error here because most 89 | * likely, there just is no connectivity. 90 | * Thus, don’t spam the user’s console. */ 91 | return NULL; 92 | } 93 | 94 | for (resp = result; resp != NULL; resp = resp->ai_next) { 95 | char *addr_string = get_sockname(resp); 96 | /* If we could not get our own address and there is more than 97 | * one result for resolving k.root-servers.net, we cannot 98 | * cache. Otherwise, no matter if we got IPv6 connectivity or 99 | * not, we will cache the (single) result and are done. */ 100 | if (!addr_string && result->ai_next != NULL) 101 | continue; 102 | 103 | if ((cached = malloc(sizeof(struct addrinfo))) == NULL) 104 | return NULL; 105 | memcpy(cached, resp, sizeof(struct addrinfo)); 106 | if ((cached->ai_addr = malloc(resp->ai_addrlen)) == NULL) { 107 | cached = NULL; 108 | return NULL; 109 | } 110 | memcpy(cached->ai_addr, resp->ai_addr, resp->ai_addrlen); 111 | freeaddrinfo(result); 112 | return addr_string; 113 | } 114 | 115 | freeaddrinfo(result); 116 | return NULL; 117 | } 118 | 119 | void print_ipv6_info(yajl_gen json_gen, char *buffer, const char *format_up, const char *format_down) { 120 | const char *walk; 121 | char *addr_string = get_ipv6_addr(); 122 | char *outwalk = buffer; 123 | 124 | if (addr_string == NULL) { 125 | START_COLOR("color_bad"); 126 | outwalk += sprintf(outwalk, "%s", format_down); 127 | END_COLOR; 128 | OUTPUT_FULL_TEXT(buffer); 129 | return; 130 | } 131 | 132 | START_COLOR("color_good"); 133 | for (walk = format_up; *walk != '\0'; walk++) { 134 | if (*walk != '%') { 135 | *(outwalk++) = *walk; 136 | continue; 137 | } 138 | 139 | if (strncmp(walk+1, "ip", strlen("ip")) == 0) { 140 | outwalk += sprintf(outwalk, "%s", addr_string); 141 | walk += strlen("ip"); 142 | } 143 | } 144 | END_COLOR; 145 | OUTPUT_FULL_TEXT(buffer); 146 | } 147 | -------------------------------------------------------------------------------- /include/i3status.h: -------------------------------------------------------------------------------- 1 | #ifndef _I3STATUS_H 2 | #define _I3STATUS_H 3 | 4 | enum { O_DZEN2, O_XMOBAR, O_I3BAR, O_TERM, O_NONE } output_format; 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define BEGINS_WITH(haystack, needle) (strncmp(haystack, needle, strlen(needle)) == 0) 15 | #define max(a, b) ((a) > (b) ? (a) : (b)) 16 | 17 | #if defined(LINUX) 18 | 19 | #define THERMAL_ZONE "/sys/class/thermal/thermal_zone%d/temp" 20 | 21 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 22 | 23 | /* this needs the coretemp module to be loaded */ 24 | #if defined(__DragonFly__) 25 | #define THERMAL_ZONE "hw.sensors.cpu%d.temp0" 26 | #else 27 | #define THERMAL_ZONE "dev.cpu.%d.temperature" 28 | #endif 29 | #define BATT_LIFE "hw.acpi.battery.life" 30 | #define BATT_TIME "hw.acpi.battery.time" 31 | #define BATT_STATE "hw.acpi.battery.state" 32 | 33 | #elif defined(__OpenBSD__) 34 | /* Default to acpitz(4) if no path is set. */ 35 | #define THERMAL_ZONE "acpitz%d" 36 | #elif defined(__NetBSD__) 37 | /* Rely on envsys(4). The key of the sensor is generally cpu%d temperature */ 38 | #define THERMAL_ZONE "cpu%d temperature" 39 | #endif 40 | 41 | #if defined(__FreeBSD_kernel__) && defined(__GLIBC__) 42 | 43 | #include 44 | #include 45 | 46 | #endif 47 | 48 | /* Allows for the definition of a variable without opening a new scope, thus 49 | * suited for usage in a macro. Idea from wmii. */ 50 | #define with(type, var, init) \ 51 | for (type var = (type)-1; (var == (type)-1) && ((var=(init)) || 1); ) 52 | 53 | #define CASE_SEC(name) \ 54 | if (BEGINS_WITH(current, name)) \ 55 | with(cfg_t *, sec, cfg_section = cfg_getsec(cfg, name)) \ 56 | if (sec != NULL) 57 | 58 | #define CASE_SEC_TITLE(name) \ 59 | if (BEGINS_WITH(current, name)) \ 60 | with(const char *, title, current + strlen(name) + 1) \ 61 | with(cfg_t *, sec, cfg_section = cfg_gettsec(cfg, name, title)) \ 62 | if (sec != NULL) 63 | 64 | /* Macro which any plugin can use to output the full_text part (when the output 65 | * format is JSON) or just output to stdout (any other output format). */ 66 | #define OUTPUT_FULL_TEXT(text) \ 67 | do { \ 68 | /* Terminate the output buffer here in any case, so that it’s \ 69 | * not forgotten in the module */ \ 70 | *outwalk = '\0'; \ 71 | if (output_format == O_I3BAR) { \ 72 | yajl_gen_string(json_gen, (const unsigned char *)"full_text", strlen("full_text")); \ 73 | yajl_gen_string(json_gen, (const unsigned char *)text, strlen(text)); \ 74 | } else { \ 75 | printf("%s", text); \ 76 | } \ 77 | } while (0) 78 | 79 | #define SEC_OPEN_MAP(name) \ 80 | do { \ 81 | if (output_format == O_I3BAR) { \ 82 | yajl_gen_map_open(json_gen); \ 83 | yajl_gen_string(json_gen, (const unsigned char *)"name", strlen("name")); \ 84 | yajl_gen_string(json_gen, (const unsigned char *)name, strlen(name)); \ 85 | } \ 86 | } while (0) 87 | 88 | #define SEC_CLOSE_MAP \ 89 | do { \ 90 | if (output_format == O_I3BAR) { \ 91 | yajl_gen_map_close(json_gen); \ 92 | } \ 93 | } while (0) 94 | 95 | #define START_COLOR(colorstr) \ 96 | do { \ 97 | if (cfg_getbool(cfg_general, "colors")) { \ 98 | const char *_val = NULL; \ 99 | if (cfg_section) \ 100 | _val = cfg_getstr(cfg_section, colorstr); \ 101 | if (!_val) \ 102 | _val = cfg_getstr(cfg_general, colorstr); \ 103 | if (output_format == O_I3BAR) { \ 104 | yajl_gen_string(json_gen, (const unsigned char *)"color", strlen("color")); \ 105 | yajl_gen_string(json_gen, (const unsigned char *)_val, strlen(_val)); \ 106 | } else { \ 107 | outwalk += sprintf(outwalk, "%s", color(colorstr)); \ 108 | } \ 109 | } \ 110 | } while (0) 111 | 112 | #define END_COLOR \ 113 | do { \ 114 | if (cfg_getbool(cfg_general, "colors") && output_format != O_I3BAR) { \ 115 | outwalk += sprintf(outwalk, "%s", endcolor()); \ 116 | } \ 117 | } while (0) 118 | 119 | #define INSTANCE(instance) \ 120 | do { \ 121 | if (output_format == O_I3BAR) { \ 122 | yajl_gen_string(json_gen, (const unsigned char *)"instance", strlen("instance")); \ 123 | yajl_gen_string(json_gen, (const unsigned char *)instance, strlen(instance)); \ 124 | } \ 125 | } while (0) 126 | 127 | 128 | typedef enum { CS_DISCHARGING, CS_CHARGING, CS_FULL } charging_status_t; 129 | 130 | /* src/general.c */ 131 | char *skip_character(char *input, char character, int amount); 132 | void die(const char *fmt, ...); 133 | bool slurp(const char *filename, char *destination, int size); 134 | 135 | /* src/output.c */ 136 | void print_seperator(); 137 | char *color(const char *colorstr); 138 | char *endcolor() __attribute__ ((pure)); 139 | void reset_cursor(void); 140 | 141 | /* src/auto_detect_format.c */ 142 | char *auto_detect_format(); 143 | 144 | /* src/print_time.c */ 145 | void set_timezone(const char *tz); 146 | 147 | void print_ipv6_info(yajl_gen json_gen, char *buffer, const char *format_up, const char *format_down); 148 | void print_disk_info(yajl_gen json_gen, char *buffer, const char *path, const char *format, const char *prefix_type); 149 | void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char *path, const char *format, const char *format_down, const char *notif_header_format, const char *notif_body_format, int low_threshold, char *threshold_type, bool last_full_capacity, bool integer_battery_capacity); 150 | void print_time(yajl_gen json_gen, char *buffer, const char *format, const char *tz, time_t t); 151 | void print_ddate(yajl_gen json_gen, char *buffer, const char *format, time_t t); 152 | const char *get_ip_addr(); 153 | void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down); 154 | void print_run_watch(yajl_gen json_gen, char *buffer, const char *title, const char *pidfile, const char *format); 155 | void print_path_exists(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format); 156 | void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const char *path, const char *format, int); 157 | void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format); 158 | void print_eth_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down); 159 | void print_load(yajl_gen json_gen, char *buffer, const char *format, const float max_threshold); 160 | void print_mpd(yajl_gen json_gen, char *buffer, const char *format, const char *format_stopped, const char *notif_header_format, const char *notif_body_format); 161 | 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); 162 | void cleanup_mpd(); 163 | bool process_runs(const char *path); 164 | 165 | /* socket file descriptor for general purposes */ 166 | extern int general_socket; 167 | 168 | extern cfg_t *cfg, *cfg_general, *cfg_section; 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2014-01-05 i3status 2.8 2 | 3 | • Fix build on GNU/Hurd 4 | • Add format_down for battery if no battery is available 5 | • Use degraded color in case volume is muted with OSS 6 | • Support mixer_idx with OSS 7 | • Enable colored output for battery on FreeBSD 8 | • print_volume: don’t return, complete the buffer first 9 | • Support colored output for CPU temperature on FreeBSD 10 | • manpage: mention i3bar in the description 11 | • manpage: be more explicit about the path option 12 | • battery: use path as i3bar JSON “instance” 13 | • print_volume(linux): Open a new mixer every time 14 | • Implement term output format 15 | • signal handler: set flag because directly calling fprintf() is unsafe 16 | • Makefile: use .SILENT and support V=1 for verbose builds 17 | • load: allow max_threshold to be a float 18 | • manpage: Add example path for CPU temperature 19 | • Fix build on NetBSD 20 | • Implement cpu usage, ethernet status, cpu temperature on NetBSD 21 | • fix slurp(), it needs to read size-1 for the trailing NUL 22 | • format detection: simplify code, handle "sh" processes in the hierarchy 23 | • default config: show %avail disk space instead of %free 24 | • Added different format string for volume in case it is muted 25 | • manpage: document format_muted 26 | • add support for path_exists directive 27 | • disk: Distinguish between IEC, SI and custom prefixes 28 | 29 | 2013-02-27 i3status 2.7 30 | 31 | • Various battery fixes for OpenBSD 32 | • Implement %speed for ethernet on OpenBSD 33 | • Implement %essid and %signal for wireless on OpenBSD 34 | • Skip a day in the Discordian calendar when St. Tib's Day has passed 35 | • colors: Handle down wireless interfaces just like ethernet interfaces 36 | • Use acpitz(4) instead of cpu(4) for temperature on OpenBSD 37 | • temperature: introduce max_threshold 38 | • temperature: allow for abitrary sensors to be selected with 'path' on OpenBSD 39 | • battery: colorize output even without discharge rate 40 | • ddate: Reduce the season day to 0-9 to properly print ordinal suffixes 41 | • add good, degraded and bad colors per module 42 | • config: introduce an utf-8 character so that editors are forced to use utf-8 43 | • ddate: Teen ordinal numbers always use a 'th' suffix. 44 | • DragonFlyBSD support added 45 | • make SIGUSR1 do nothing, so that killall -USR1 i3status will force an update 46 | • changing volume color from bad to degraded if muted 47 | • make refreshs align with minutes 48 | • wifi: properly display ad-hoc networks 49 | • Bugfix: output auto detect: properly detect ambiguous situations 50 | • Add tztime module to support multiple different timezones 51 | • battery: add option to show capacity without decimals 52 | • Add colorized output for load avg 53 | • ipv6: properly handle colors for output_format == xmobar 54 | • contrib: fix premature insetion of status message into JSON 55 | 56 | 2012-10-03 i3status 2.6 57 | 58 | • Error handling: Properly output JSON errors in the battery module 59 | • Colorize battery output if remaining time/percentage below threshold 60 | • Colorize volume in red if muted 61 | • Include a Perl and Python example wrapper script for i3status’s JSON output 62 | • Avoid division by zero when calculating CPU usage 63 | • Add consumption to battery info 64 | • Use correct units for battery calculations 65 | • Add percentage values for disk info 66 | • Save the DNS lookup for k.root-servers.net in the ipv6 module 67 | 68 | 2012-05-11 i3status 2.5.1 69 | 70 | • Handle %d with non-default CPU temperature path 71 | • Improve error message when the cpu temperature cannot be read 72 | • Remove cpu_temperature from the default config 73 | • Error handling: Never output null as full_text (JSON), prefix messages with i3status 74 | 75 | 2012-05-02 i3status 2.5 76 | 77 | • implement the i3bar JSON protocol 78 | • Fix configuration paths in error message 79 | • manpage: add a "format" example for run_watch 80 | • manpage: add a section explaining why we don’t want RAM usage etc. 81 | • manpage: add "exit 1" to example shell script. 82 | • manpage: document %emptytime to the battery part 83 | • various patches for OpenBSD 84 | 85 | 2011-12-27 i3status 2.4 86 | 87 | • Support wifi, cpu usage, volume, disk usage on FreeBSD 88 | • Don’t exit, but display an error when CPU temperature/usage/load is not 89 | available 90 | • manpage: load format is %1min %5min %15min 91 | • Don’t use a default order, so that config files can use += everywhere 92 | • Interpret configfiles case-insensitive 93 | • battery: implement path option for batteries with non-standard paths 94 | 95 | 2011-07-21 i3status 2.3 96 | 97 | • config search order is now ~/.i3status.conf, ~/.config/i3status/config, 98 | /etc/i3status/config, then /etc/xdg/i3status/config 99 | • battery: use POWER_NOW if CURRENT_NOW is not available (linux >= 2.6.36) 100 | • use kill(0, pid) instead of /proc to check if a process is alive 101 | • cache DNS query results also if there is no IPv6 connectivity 102 | • implement the 'path' option for cpu_temperature 103 | • add cpu_usage module 104 | • automatically detect the output format unless configured specifically 105 | • Bugfix: get time at the beginning of the loop 106 | • Bugfix: respect locale settings (for %c in date format) 107 | • debian: use [linux-any] in dependencies (Closes: #634491) 108 | 109 | 2010-09-22 i3status 2.2 110 | 111 | • Implement a discordian date module (like ddate(1)) 112 | • Implement ALSA volume support 113 | • disk: implement %avail (different from %free with regard to the reserved 114 | space of your filesystem) 115 | • wireless: display quality relative to the wireless maximum quality. 116 | • wireless: display connection speed (bitrate) 117 | • wireless: custom colors for link quality 118 | • Bugfix: Treat an interface as 'down' when it cannot be found in 119 | /proc/net/wireless 120 | • Bugfix: Correctly check for interface up/down status 121 | 122 | 2010-04-10 i3status 2.1 123 | 124 | • battery: implement %emptytime, the time of day when your battery is empty 125 | • ipv6: cache DNS query lookups (for finding the K root server) 126 | • disk: also round when printing TB 127 | • wireless: display no IP instead of (null) 128 | • instead of a relative sleep(1), sleep until the full second 129 | • colorize ethernet output 130 | • use cap_net_admin instead of running i3status as root for getting 131 | ethernet link speed 132 | • ipv6: don't require a title for config option 133 | • ipv6: provide format strings for ipv6 up and ipv6 down 134 | 135 | 2009-10-27 i3status 2.0 136 | 137 | • add support for disk info (free/used/ins) 138 | • add support for displaying the ESSID of the wireless interface 139 | • add support for getting the public IPv6 address of the system 140 | • all "modules" now support format strings 141 | • switch to libconfuse for parsing the config file 142 | • merge support for FreeBSD for many "modules" 143 | • drop support for wmii, add support for xmobar 144 | 145 | 2009-06-21 i3status 1.2 146 | 147 | • Handle SIGPIPE to avoid zombie processes 148 | 149 | 2009-06-17 i3status 1.1 150 | 151 | • Implement getting temperature from thermal zones (Thanks atsutane) 152 | -------------------------------------------------------------------------------- /src/print_ddate.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "i3status.h" 10 | 11 | /* define fixed output-Strings */ 12 | char *season_long[5] = { 13 | "Chaos", 14 | "Discord", 15 | "Confusion", 16 | "Bureaucracy", 17 | "The Aftermath" 18 | }; 19 | 20 | char *season_short[5] = { 21 | "Chs", 22 | "Dsc", 23 | "Cfn", 24 | "Bcy", 25 | "Afm" 26 | }; 27 | 28 | char *day_long[5] = { 29 | "Sweetmorn", 30 | "Boomtime", 31 | "Pungenday", 32 | "Prickle-Prickle", 33 | "Setting Orange" 34 | }; 35 | 36 | char *day_short[5] = { 37 | "SM", 38 | "BT", 39 | "PD", 40 | "PP", 41 | "SO" 42 | }; 43 | 44 | char *holidays[10] = { 45 | "Mungday", 46 | "Mojoday", 47 | "Syaday", 48 | "Zaraday", 49 | "Maladay", 50 | "Chaoflux", 51 | "Discoflux", 52 | "Confuflux", 53 | "Bureflux", 54 | "Afflux" 55 | }; 56 | 57 | /* A helper-struct, taking the discordian date */ 58 | struct disc_time { 59 | int year; 60 | int season; 61 | int week_day; 62 | int season_day; 63 | int st_tibs_day; 64 | }; 65 | 66 | /* Print the date *dt in format *format */ 67 | static int format_output(char *outwalk, char *format, struct disc_time *dt) { 68 | char *orig_outwalk = outwalk; 69 | char *i; 70 | char *tibs_end = 0; 71 | 72 | for (i = format; *i != '\0'; i++) { 73 | if (*i != '%') { 74 | *(outwalk++) = *i; 75 | continue; 76 | } 77 | switch (*(i+1)) { 78 | /* Weekday in long and abbreviation */ 79 | case 'A': 80 | outwalk += sprintf(outwalk, "%s", day_long[dt->week_day]); 81 | break; 82 | case 'a': 83 | outwalk += sprintf(outwalk, "%s", day_short[dt->week_day]); 84 | break; 85 | /* Season in long and abbreviation */ 86 | case 'B': 87 | outwalk += sprintf(outwalk, "%s", season_long[dt->season]); 88 | break; 89 | case 'b': 90 | outwalk += sprintf(outwalk, "%s", season_short[dt->season]); 91 | break; 92 | /* Day of the season (ordinal and cardinal) */ 93 | case 'd': 94 | outwalk += sprintf(outwalk, "%d", dt->season_day + 1); 95 | break; 96 | case 'e': 97 | outwalk += sprintf(outwalk, "%d", dt->season_day + 1); 98 | if (dt->season_day > 9 && dt->season_day < 13) { 99 | outwalk += sprintf(outwalk, "th"); 100 | break; 101 | } 102 | 103 | switch (dt->season_day % 10) { 104 | case 0: 105 | outwalk += sprintf(outwalk, "st"); 106 | break; 107 | case 1: 108 | outwalk += sprintf(outwalk, "nd"); 109 | break; 110 | case 2: 111 | outwalk += sprintf(outwalk, "rd"); 112 | break; 113 | default: 114 | outwalk += sprintf(outwalk, "th"); 115 | break; 116 | } 117 | break; 118 | /* YOLD */ 119 | case 'Y': 120 | outwalk += sprintf(outwalk, "%d", dt->year); 121 | break; 122 | /* Holidays */ 123 | case 'H': 124 | if (dt->season_day == 4) { 125 | outwalk += sprintf(outwalk, "%s", holidays[dt->season]); 126 | } 127 | if (dt->season_day == 49) { 128 | outwalk += sprintf(outwalk, "%s", holidays[dt->season + 5]); 129 | } 130 | break; 131 | /* Stop parsing the format string, except on Holidays */ 132 | case 'N': 133 | if (dt->season_day != 4 && dt->season_day != 49) { 134 | return (outwalk - orig_outwalk); 135 | } 136 | break; 137 | /* Newline- and Tabbing-characters */ 138 | case 'n': 139 | outwalk += sprintf(outwalk, "\n"); 140 | break; 141 | case 't': 142 | outwalk += sprintf(outwalk, "\t"); 143 | break; 144 | /* The St. Tib's Day replacement */ 145 | case '{': 146 | tibs_end = strstr(i, "%}"); 147 | if (tibs_end == NULL) { 148 | i++; 149 | break; 150 | } 151 | if (dt->st_tibs_day) { 152 | /* We outpt "St. Tib's Day... */ 153 | outwalk += sprintf(outwalk, "St. Tib's Day"); 154 | } else { 155 | /* ...or parse the substring between %{ and %} ... */ 156 | *tibs_end = '\0'; 157 | outwalk += format_output(outwalk, i + 2, dt); 158 | *tibs_end = '%'; 159 | } 160 | /* ...and continue with the rest */ 161 | i = tibs_end; 162 | break; 163 | case '}': 164 | i++; 165 | break; 166 | default: 167 | /* No escape-sequence, so we just skip */ 168 | outwalk += sprintf(outwalk, "%%%c", *(i+1)); 169 | break; 170 | } 171 | i++; 172 | } 173 | return (outwalk - orig_outwalk); 174 | } 175 | 176 | /* Get the current date and convert it to discordian */ 177 | struct disc_time *get_ddate(struct tm *current_tm) { 178 | static struct disc_time dt; 179 | 180 | if (current_tm == NULL) 181 | return NULL; 182 | 183 | /* We have to know, whether we have to insert St. Tib's Day, so whether it's a leap 184 | year in gregorian calendar */ 185 | int is_leap_year = !(current_tm->tm_year % 4) && 186 | (!(current_tm->tm_year % 400) || current_tm->tm_year % 100); 187 | 188 | /* If St. Tib's Day has passed, it will be necessary to skip a day. */ 189 | int yday = current_tm->tm_yday; 190 | 191 | if (is_leap_year && yday == 59) { 192 | /* On St. Tibs Day we don't have to define a date */ 193 | dt.st_tibs_day = 1; 194 | } else { 195 | dt.st_tibs_day = 0; 196 | if (is_leap_year && yday > 59) 197 | yday -= 1; 198 | 199 | dt.season_day = yday % 73; 200 | dt.week_day = yday % 5; 201 | } 202 | dt.year = current_tm->tm_year + 3066; 203 | dt.season = yday / 73; 204 | return &dt; 205 | } 206 | 207 | void print_ddate(yajl_gen json_gen, char *buffer, const char *format, time_t t) { 208 | char *outwalk = buffer; 209 | static char *form = NULL; 210 | struct tm current_tm; 211 | struct disc_time *dt; 212 | set_timezone(NULL); /* Use local time. */ 213 | localtime_r(&t, ¤t_tm); 214 | if ((dt = get_ddate(¤t_tm)) == NULL) 215 | return; 216 | if (form == NULL) 217 | if ((form = malloc(strlen(format) + 1)) == NULL) 218 | return; 219 | strcpy(form, format); 220 | outwalk += format_output(outwalk, form, dt); 221 | OUTPUT_FULL_TEXT(buffer); 222 | } 223 | -------------------------------------------------------------------------------- /src/print_cpu_temperature.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "i3status.h" 10 | 11 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 12 | #include 13 | #include 14 | #include 15 | #define TZ_ZEROC 2732 16 | #define TZ_KELVTOC(x) (((x) - TZ_ZEROC) / 10), abs(((x) - TZ_ZEROC) % 10) 17 | #define TZ_AVG(x) ((x) - TZ_ZEROC) / 10 18 | #endif 19 | 20 | #if defined(__OpenBSD__) 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define MUKTOC(v) ((v - 273150000) / 1000000.0) 29 | #endif 30 | 31 | #if defined(__NetBSD__) 32 | #include 33 | #include 34 | #include 35 | 36 | #define MUKTOC(v) ((v - 273150000) / 1000000.0) 37 | #endif 38 | 39 | 40 | static char *thermal_zone; 41 | 42 | /* 43 | * Reads the CPU temperature from /sys/class/thermal/thermal_zone0/temp and 44 | * returns the temperature in degree celcius. 45 | * 46 | */ 47 | void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const char *path, const char *format, int max_threshold) { 48 | char *outwalk = buffer; 49 | #ifdef THERMAL_ZONE 50 | const char *walk; 51 | bool colorful_output = false; 52 | 53 | if (thermal_zone == NULL) { 54 | if (path == NULL) 55 | asprintf(&thermal_zone, THERMAL_ZONE, zone); 56 | else 57 | asprintf(&thermal_zone, path, zone); 58 | } 59 | path = thermal_zone; 60 | 61 | INSTANCE(path); 62 | 63 | for (walk = format; *walk != '\0'; walk++) { 64 | if (*walk != '%') { 65 | *(outwalk++) = *walk; 66 | continue; 67 | } 68 | 69 | if (BEGINS_WITH(walk+1, "degrees")) { 70 | #if defined(LINUX) 71 | static char buf[16]; 72 | long int temp; 73 | if (!slurp(path, buf, sizeof(buf))) 74 | goto error; 75 | temp = strtol(buf, NULL, 10); 76 | if (temp == LONG_MIN || temp == LONG_MAX || temp <= 0) 77 | *(outwalk++) = '?'; 78 | else { 79 | if ((temp/1000) >= max_threshold) { 80 | START_COLOR("color_bad"); 81 | colorful_output = true; 82 | } 83 | outwalk += sprintf(outwalk, "%ld", (temp/1000)); 84 | if (colorful_output) { 85 | END_COLOR; 86 | colorful_output = false; 87 | } 88 | } 89 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 90 | int sysctl_rslt; 91 | size_t sysctl_size = sizeof(sysctl_rslt); 92 | if (sysctlbyname(path, &sysctl_rslt, &sysctl_size, NULL, 0)) 93 | goto error; 94 | 95 | if (TZ_AVG(sysctl_rslt) >= max_threshold) { 96 | START_COLOR("color_bad"); 97 | colorful_output = true; 98 | } 99 | outwalk += sprintf(outwalk, "%d.%d", TZ_KELVTOC(sysctl_rslt)); 100 | if (colorful_output) { 101 | END_COLOR; 102 | colorful_output = false; 103 | } 104 | 105 | #elif defined(__OpenBSD__) 106 | struct sensordev sensordev; 107 | struct sensor sensor; 108 | size_t sdlen, slen; 109 | int dev, numt, mib[5] = { CTL_HW, HW_SENSORS, 0, 0, 0 }; 110 | 111 | sdlen = sizeof(sensordev); 112 | slen = sizeof(sensor); 113 | 114 | for (dev = 0; ; dev++) { 115 | mib[2] = dev; 116 | if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { 117 | if (errno == ENXIO) 118 | continue; 119 | if (errno == ENOENT) 120 | break; 121 | goto error; 122 | } 123 | /* 'path' is the node within the full path (defaults to acpitz0). */ 124 | if (strncmp(sensordev.xname, path, strlen(path)) == 0) { 125 | mib[3] = SENSOR_TEMP; 126 | /* Limit to temo0, but should retrieve from a full path... */ 127 | for (numt = 0; numt < 1 /*sensordev.maxnumt[SENSOR_TEMP]*/; numt++) { 128 | mib[4] = numt; 129 | if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { 130 | if (errno != ENOENT) { 131 | warn("sysctl"); 132 | continue; 133 | } 134 | } 135 | if ((int)MUKTOC(sensor.value) >= max_threshold) { 136 | START_COLOR("color_bad"); 137 | colorful_output = true; 138 | } 139 | 140 | outwalk += sprintf(outwalk, "%.2f", MUKTOC(sensor.value)); 141 | 142 | if (colorful_output) { 143 | END_COLOR; 144 | colorful_output = false; 145 | } 146 | } 147 | } 148 | } 149 | #elif defined(__NetBSD__) 150 | int fd, rval; 151 | bool err = false; 152 | prop_dictionary_t dict; 153 | prop_array_t array; 154 | prop_object_iterator_t iter; 155 | prop_object_iterator_t iter2; 156 | prop_object_t obj, obj2, obj3; 157 | 158 | fd = open("/dev/sysmon", O_RDONLY); 159 | if (fd == -1) 160 | goto error; 161 | 162 | rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict); 163 | if (rval == -1) { 164 | err = true; 165 | goto error_netbsd1; 166 | } 167 | 168 | /* No drivers registered? */ 169 | if (prop_dictionary_count(dict) == 0) { 170 | err = true; 171 | goto error_netbsd2; 172 | } 173 | 174 | /* print sensors for all devices registered */ 175 | iter = prop_dictionary_iterator(dict); 176 | if (iter == NULL) { 177 | err = true; 178 | goto error_netbsd2; 179 | } 180 | 181 | /* iterate over the dictionary returned by the kernel */ 182 | while ((obj = prop_object_iterator_next(iter)) != NULL) { 183 | array = prop_dictionary_get_keysym(dict, obj); 184 | if (prop_object_type(array) != PROP_TYPE_ARRAY) { 185 | err = true; 186 | goto error_netbsd3; 187 | } 188 | iter2 = prop_array_iterator(array); 189 | if (!iter2) { 190 | err = true; 191 | goto error_netbsd3; 192 | } 193 | 194 | /* iterate over the array of dictionaries */ 195 | while ((obj2 = prop_object_iterator_next(iter2)) != NULL) { 196 | obj3 = prop_dictionary_get(obj2, "description"); 197 | if (obj3 && 198 | strcmp(path, prop_string_cstring_nocopy(obj3)) == 0) 199 | { 200 | obj3 = prop_dictionary_get(obj2, "cur-value"); 201 | float temp = MUKTOC(prop_number_integer_value(obj3)); 202 | if ((int)temp >= max_threshold) { 203 | START_COLOR("color_bad"); 204 | colorful_output = true; 205 | } 206 | 207 | outwalk += sprintf(outwalk, "%.2f", temp); 208 | 209 | if (colorful_output) { 210 | END_COLOR; 211 | colorful_output = false; 212 | } 213 | break; 214 | } 215 | 216 | } 217 | prop_object_iterator_release(iter2); 218 | } 219 | error_netbsd3: 220 | prop_object_iterator_release(iter); 221 | error_netbsd2: 222 | prop_object_release(dict); 223 | error_netbsd1: 224 | close(fd); 225 | if (err) goto error; 226 | 227 | #endif 228 | 229 | 230 | walk += strlen("degrees"); 231 | } 232 | } 233 | OUTPUT_FULL_TEXT(buffer); 234 | return; 235 | error: 236 | #endif 237 | OUTPUT_FULL_TEXT("cant read temp"); 238 | (void)fputs("i3status: Cannot read temperature. Verify that you have a thermal zone in /sys/class/thermal or disable the cpu_temperature module in your i3status config.\n", stderr); 239 | } 240 | -------------------------------------------------------------------------------- /src/print_wireless_info.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef LINUX 8 | #include 9 | #else 10 | #ifndef __FreeBSD__ 11 | #define IW_ESSID_MAX_SIZE 32 12 | #endif 13 | #endif 14 | 15 | #ifdef __FreeBSD__ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #define IW_ESSID_MAX_SIZE IEEE80211_NWID_LEN 26 | #endif 27 | 28 | #ifdef __DragonFly__ 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #define IW_ESSID_MAX_SIZE IEEE80211_NWID_LEN 39 | #endif 40 | 41 | #ifdef __OpenBSD__ 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #endif 51 | 52 | #include "i3status.h" 53 | 54 | #define WIRELESS_INFO_FLAG_HAS_ESSID (1 << 0) 55 | #define WIRELESS_INFO_FLAG_HAS_QUALITY (1 << 1) 56 | #define WIRELESS_INFO_FLAG_HAS_SIGNAL (1 << 2) 57 | #define WIRELESS_INFO_FLAG_HAS_NOISE (1 << 3) 58 | 59 | #define PERCENT_VALUE(value, total) ((int)(value * 100 / (float)total + 0.5f)) 60 | 61 | typedef struct { 62 | int flags; 63 | char essid[IW_ESSID_MAX_SIZE + 1]; 64 | int quality; 65 | int quality_max; 66 | int quality_average; 67 | int signal_level; 68 | int signal_level_max; 69 | int noise_level; 70 | int noise_level_max; 71 | int bitrate; 72 | } wireless_info_t; 73 | 74 | static int get_wireless_info(const char *interface, wireless_info_t *info) { 75 | memset(info, 0, sizeof(wireless_info_t)); 76 | 77 | #ifdef LINUX 78 | int skfd = iw_sockets_open(); 79 | if (skfd < 0) { 80 | perror("iw_sockets_open"); 81 | return 0; 82 | } 83 | 84 | wireless_config wcfg; 85 | if (iw_get_basic_config(skfd, interface, &wcfg) < 0) { 86 | close(skfd); 87 | return 0; 88 | } 89 | 90 | if (wcfg.has_essid && wcfg.essid_on) { 91 | info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID; 92 | strncpy(&info->essid[0], wcfg.essid, IW_ESSID_MAX_SIZE); 93 | info->essid[IW_ESSID_MAX_SIZE] = '\0'; 94 | } 95 | 96 | /* If the function iw_get_stats does not return proper stats, the 97 | wifi is considered as down. 98 | Since ad-hoc network does not have theses stats, we need to return 99 | here for this mode. */ 100 | if (wcfg.mode == 1) { 101 | close(skfd); 102 | return 1; 103 | } 104 | 105 | /* Wireless quality is a relative value in a driver-specific range. 106 | Signal and noise level can be either relative or absolute values 107 | in dBm. Furthermore, noise and quality can be expressed directly 108 | in dBm or in RCPI (802.11k), which we convert to dBm. When those 109 | values are expressed directly in dBm, they range from -192 to 63, 110 | and since the values are packed into 8 bits, we need to perform 111 | 8-bit arithmetic on them. Assume absolute values if everything 112 | else fails (driver bug). */ 113 | 114 | iwrange range; 115 | if (iw_get_range_info(skfd, interface, &range) < 0) { 116 | close(skfd); 117 | return 0; 118 | } 119 | 120 | iwstats stats; 121 | if (iw_get_stats(skfd, interface, &stats, &range, 1) < 0) { 122 | close(skfd); 123 | return 0; 124 | } 125 | 126 | if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) { 127 | if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) { 128 | info->quality = stats.qual.qual; 129 | info->quality_max = range.max_qual.qual; 130 | info->quality_average = range.avg_qual.qual; 131 | info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY; 132 | } 133 | 134 | if (stats.qual.updated & IW_QUAL_RCPI) { 135 | if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { 136 | info->signal_level = stats.qual.level / 2.0 - 110 + 0.5; 137 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 138 | } 139 | if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { 140 | info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5; 141 | info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; 142 | } 143 | } 144 | else { 145 | if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) { 146 | if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { 147 | info->signal_level = stats.qual.level; 148 | if (info->signal_level > 63) 149 | info->signal_level -= 256; 150 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 151 | } 152 | if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { 153 | info->noise_level = stats.qual.noise; 154 | if (info->noise_level > 63) 155 | info->noise_level -= 256; 156 | info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; 157 | } 158 | } 159 | else { 160 | if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { 161 | info->signal_level = stats.qual.level; 162 | info->signal_level_max = range.max_qual.level; 163 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 164 | } 165 | if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { 166 | info->noise_level = stats.qual.noise; 167 | info->noise_level_max = range.max_qual.noise; 168 | info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; 169 | } 170 | } 171 | } 172 | } 173 | else { 174 | if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) { 175 | info->quality = stats.qual.qual; 176 | info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY; 177 | } 178 | if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { 179 | info->quality = stats.qual.level; 180 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 181 | } 182 | if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { 183 | info->quality = stats.qual.noise; 184 | info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; 185 | } 186 | } 187 | 188 | struct iwreq wrq; 189 | if (iw_get_ext(skfd, interface, SIOCGIWRATE, &wrq) >= 0) 190 | info->bitrate = wrq.u.bitrate.value; 191 | 192 | close(skfd); 193 | return 1; 194 | #endif 195 | #if defined(__FreeBSD__) || defined(__DragonFly__) 196 | int s, len, inwid; 197 | uint8_t buf[24 * 1024], *cp; 198 | struct ieee80211req na; 199 | char network_id[IEEE80211_NWID_LEN + 1]; 200 | 201 | if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 202 | return (0); 203 | 204 | memset(&na, 0, sizeof(na)); 205 | strlcpy(na.i_name, interface, sizeof(na.i_name)); 206 | na.i_type = IEEE80211_IOC_SSID; 207 | na.i_data = &info->essid[0]; 208 | na.i_len = IEEE80211_NWID_LEN + 1; 209 | if ((inwid = ioctl(s, SIOCG80211, (caddr_t)&na)) == -1) { 210 | close(s); 211 | return (0); 212 | } 213 | if (inwid == 0) { 214 | if (na.i_len <= IEEE80211_NWID_LEN) 215 | len = na.i_len + 1; 216 | else 217 | len = IEEE80211_NWID_LEN + 1; 218 | info->essid[len -1] = '\0'; 219 | } else { 220 | close(s); 221 | return (0); 222 | } 223 | info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID; 224 | 225 | memset(&na, 0, sizeof(na)); 226 | strlcpy(na.i_name, interface, sizeof(na.i_name)); 227 | na.i_type = IEEE80211_IOC_SCAN_RESULTS; 228 | na.i_data = buf; 229 | na.i_len = sizeof(buf); 230 | 231 | if (ioctl(s, SIOCG80211, (caddr_t)&na) == -1) { 232 | printf("fail\n"); 233 | close(s); 234 | return (0); 235 | } 236 | 237 | close(s); 238 | len = na.i_len; 239 | cp = buf; 240 | struct ieee80211req_scan_result *sr; 241 | uint8_t *vp; 242 | sr = (struct ieee80211req_scan_result *)cp; 243 | vp = (u_int8_t *)(sr + 1); 244 | strlcpy(network_id, (const char *)vp, sr->isr_ssid_len + 1); 245 | if (!strcmp(network_id, &info->essid[0])) { 246 | info->signal_level = sr->isr_rssi; 247 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 248 | info->noise_level = sr->isr_noise; 249 | info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; 250 | info->quality = sr->isr_intval; 251 | info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY; 252 | } 253 | 254 | return 1; 255 | #endif 256 | #ifdef __OpenBSD__ 257 | struct ifreq ifr; 258 | struct ieee80211_bssid bssid; 259 | struct ieee80211_nwid nwid; 260 | struct ieee80211_nodereq nr; 261 | 262 | struct ether_addr ea; 263 | 264 | int s, len, ibssid, inwid; 265 | u_int8_t zero_bssid[IEEE80211_ADDR_LEN]; 266 | 267 | if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 268 | return (0); 269 | 270 | memset(&ifr, 0, sizeof(ifr)); 271 | ifr.ifr_data = (caddr_t)&nwid; 272 | (void)strlcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); 273 | inwid = ioctl(s, SIOCG80211NWID, (caddr_t)&ifr); 274 | 275 | memset(&bssid, 0, sizeof(bssid)); 276 | strlcpy(bssid.i_name, interface, sizeof(bssid.i_name)); 277 | ibssid = ioctl(s, SIOCG80211BSSID, &bssid); 278 | 279 | if (ibssid != 0 || inwid != 0) { 280 | close(s); 281 | return 0; 282 | } 283 | 284 | /* NWID */ 285 | { 286 | if (nwid.i_len <= IEEE80211_NWID_LEN) 287 | len = nwid.i_len + 1; 288 | else 289 | len = IEEE80211_NWID_LEN + 1; 290 | 291 | strncpy(&info->essid[0], nwid.i_nwid, len); 292 | info->essid[IW_ESSID_MAX_SIZE] = '\0'; 293 | info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID; 294 | } 295 | 296 | /* Signal strength */ 297 | { 298 | memset(&zero_bssid, 0, sizeof(zero_bssid)); 299 | if (ibssid == 0 && memcmp(bssid.i_bssid, zero_bssid, IEEE80211_ADDR_LEN) != 0) { 300 | memcpy(&ea.ether_addr_octet, bssid.i_bssid, sizeof(ea.ether_addr_octet)); 301 | 302 | bzero(&nr, sizeof(nr)); 303 | bcopy(bssid.i_bssid, &nr.nr_macaddr, sizeof(nr.nr_macaddr)); 304 | strlcpy(nr.nr_ifname, interface, sizeof(nr.nr_ifname)); 305 | 306 | if (ioctl(s, SIOCG80211NODE, &nr) == 0 && nr.nr_rssi) { 307 | if (nr.nr_max_rssi) 308 | info->signal_level_max = IEEE80211_NODEREQ_RSSI(&nr); 309 | else 310 | info->signal_level = nr.nr_rssi; 311 | 312 | info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; 313 | } 314 | } 315 | } 316 | 317 | close(s); 318 | return 1; 319 | #endif 320 | return 0; 321 | } 322 | 323 | void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down) { 324 | const char *walk; 325 | char *outwalk = buffer; 326 | wireless_info_t info; 327 | 328 | INSTANCE(interface); 329 | 330 | const char *ip_address = get_ip_addr(interface); 331 | if (ip_address == NULL) { 332 | START_COLOR("color_bad"); 333 | outwalk += sprintf(outwalk, "%s", format_down); 334 | goto out; 335 | } 336 | 337 | if (get_wireless_info(interface, &info)) { 338 | walk = format_up; 339 | if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) 340 | START_COLOR((info.quality < info.quality_average ? "color_degraded" : "color_good")); 341 | else 342 | START_COLOR("color_good"); 343 | } else { 344 | walk = format_down; 345 | START_COLOR("color_bad"); 346 | } 347 | 348 | for (; *walk != '\0'; walk++) { 349 | if (*walk != '%') { 350 | *(outwalk++) = *walk; 351 | continue; 352 | } 353 | 354 | if (BEGINS_WITH(walk+1, "quality")) { 355 | if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) { 356 | if (info.quality_max) 357 | outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.quality, info.quality_max)); 358 | else 359 | outwalk += sprintf(outwalk, "%d", info.quality); 360 | } else { 361 | *(outwalk++) = '?'; 362 | } 363 | walk += strlen("quality"); 364 | } 365 | 366 | if (BEGINS_WITH(walk+1, "signal")) { 367 | if (info.flags & WIRELESS_INFO_FLAG_HAS_SIGNAL) { 368 | if (info.signal_level_max) 369 | outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.signal_level, info.signal_level_max)); 370 | else 371 | outwalk += sprintf(outwalk, "%d dBm", info.signal_level); 372 | } else { 373 | *(outwalk++) = '?'; 374 | } 375 | walk += strlen("signal"); 376 | } 377 | 378 | if (BEGINS_WITH(walk+1, "noise")) { 379 | if (info.flags & WIRELESS_INFO_FLAG_HAS_NOISE) { 380 | if (info.noise_level_max) 381 | outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.noise_level, info.noise_level_max)); 382 | else 383 | outwalk += sprintf(outwalk, "%d dBm", info.noise_level); 384 | } else { 385 | *(outwalk++) = '?'; 386 | } 387 | walk += strlen("noise"); 388 | } 389 | 390 | if (BEGINS_WITH(walk+1, "essid")) { 391 | if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID) 392 | outwalk += sprintf(outwalk, "%s", info.essid); 393 | else 394 | *(outwalk++) = '?'; 395 | walk += strlen("essid"); 396 | } 397 | 398 | if (BEGINS_WITH(walk+1, "ip")) { 399 | outwalk += sprintf(outwalk, "%s", ip_address); 400 | walk += strlen("ip"); 401 | } 402 | 403 | #ifdef LINUX 404 | if (BEGINS_WITH(walk+1, "bitrate")) { 405 | char br_buffer[128]; 406 | 407 | iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate); 408 | 409 | outwalk += sprintf(outwalk, "%s", br_buffer); 410 | walk += strlen("bitrate"); 411 | } 412 | #endif 413 | } 414 | 415 | out: 416 | END_COLOR; 417 | OUTPUT_FULL_TEXT(buffer); 418 | } 419 | -------------------------------------------------------------------------------- /man/i3status.man: -------------------------------------------------------------------------------- 1 | i3status(1) 2 | =========== 3 | Michael Stapelberg 4 | v2.8, January 2014 5 | 6 | == NAME 7 | 8 | i3status - Generates a status line for i3bar, dzen2 or xmobar 9 | 10 | == SYNOPSIS 11 | 12 | i3status [-c configfile] [-h] [-v] 13 | 14 | == OPTIONS 15 | 16 | -c:: 17 | Specifies an alternate configuration file path. By default, i3status looks for 18 | configuration files in the following order: 19 | 20 | 1. ~/.i3status.conf 21 | 2. ~/.config/i3status/config (or $XDG_CONFIG_HOME/i3status/config if set) 22 | 3. /etc/i3status.conf 23 | 4. /etc/xdg/i3status/config (or $XDG_CONFIG_DIRS/i3status/config if set) 24 | 25 | == DESCRIPTION 26 | 27 | i3status is a small program (about 1500 SLOC) for generating a status bar for 28 | i3bar, dzen2, xmobar or similar programs. It is designed to be very 29 | efficient by issuing a very small number of system calls, as one generally 30 | wants to update such a status line every second. This ensures that even under 31 | high load, your status bar is updated correctly. Also, it saves a bit of energy 32 | by not hogging your CPU as much as spawning the corresponding amount of shell 33 | commands would. 34 | 35 | == CONFIGURATION 36 | 37 | The basic idea of i3status is that you can specify which "modules" should 38 | be used (the order directive). You can then configure each module with its 39 | own section. For every module, you can specify the output format. See below 40 | for a complete reference. 41 | 42 | .Sample configuration 43 | ------------------------------------------------------------- 44 | general { 45 | output_format = "dzen2" 46 | colors = true 47 | interval = 5 48 | } 49 | 50 | order += "ipv6" 51 | order += "disk /" 52 | order += "run_watch DHCP" 53 | order += "run_watch VPNC" 54 | order += "path_exists VPN" 55 | order += "wireless wlan0" 56 | order += "ethernet eth0" 57 | order += "battery 0" 58 | order += "cpu_temperature 0" 59 | order += "load" 60 | order += "tztime local" 61 | order += "tztime berlin" 62 | 63 | wireless wlan0 { 64 | format_up = "W: (%quality at %essid, %bitrate) %ip" 65 | format_down = "W: down" 66 | } 67 | 68 | ethernet eth0 { 69 | # if you use %speed, i3status requires the cap_net_admin capability 70 | format_up = "E: %ip (%speed)" 71 | format_down = "E: down" 72 | } 73 | 74 | battery 0 { 75 | format = "%status %percentage %remaining %emptytime" 76 | format_down = "No battery" 77 | path = "/sys/class/power_supply/BAT%d/uevent" 78 | low_threshold = 10 79 | } 80 | 81 | run_watch DHCP { 82 | pidfile = "/var/run/dhclient*.pid" 83 | } 84 | 85 | run_watch VPNC { 86 | # file containing the PID of a vpnc process 87 | pidfile = "/var/run/vpnc/pid" 88 | } 89 | 90 | path_exists VPN { 91 | # path exists when a VPN tunnel launched by nmcli/nm-applet is active 92 | path = "/proc/sys/net/ipv4/conf/tun0" 93 | } 94 | 95 | tztime local { 96 | format = "%Y-%m-%d %H:%M:%S" 97 | } 98 | 99 | tztime berlin { 100 | format = "%Y-%m-%d %H:%M:%S %Z" 101 | timezone = "Europe/Berlin" 102 | } 103 | 104 | load { 105 | format = "%5min" 106 | } 107 | 108 | cpu_temperature 0 { 109 | format = "T: %degrees °C" 110 | path = "/sys/devices/platform/coretemp.0/temp1_input" 111 | } 112 | 113 | disk "/" { 114 | format = "%free" 115 | } 116 | ------------------------------------------------------------- 117 | 118 | === General 119 | 120 | The +colors+ directive will disable all colors if you set it to +false+. You can 121 | also specify the colors that will be used to display "good", "degraded" or "bad" 122 | values using the +color_good+, +color_degraded+ or +color_bad+ directives, 123 | respectively. Those directives are only used if color support is not disabled by 124 | the +colors+ directive. The input format for color values is the canonical RGB 125 | hexadecimal triplet (with no separators between the colors), prefixed by a hash 126 | character ("#"). 127 | 128 | *Example configuration*: 129 | ------------------------------------------------------------- 130 | color_good = "#00FF00" 131 | ------------------------------------------------------------- 132 | 133 | Likewise, you can use the +color_separator+ directive to specify the color that 134 | will be used to paint the separator bar. The separator is always output in 135 | color, even when colors are disabled by the +colors+ directive. 136 | 137 | The +interval+ directive specifies the time in seconds for which i3status will 138 | sleep before printing the next status line. 139 | 140 | Using +output_format+ you can chose which format strings i3status should 141 | use in its output. Currently available are: 142 | 143 | i3bar:: 144 | i3bar comes with i3 and provides a workspace bar which does the right thing in 145 | multi-monitor situations. It also comes with tray support and can display the 146 | i3status output. This output type uses JSON to pass as much meta-information to 147 | i3bar as possible (like colors, which blocks can be shortened in which way, 148 | etc.). 149 | dzen2:: 150 | Dzen is a general purpose messaging, notification and menuing program for X11. 151 | It was designed to be scriptable in any language and integrate well with window 152 | managers like dwm, wmii and xmonad though it will work with any windowmanger 153 | xmobar:: 154 | xmobar is a minimalistic, text based, status bar. It was designed to work 155 | with the xmonad Window Manager. 156 | term:: 157 | Use ANSI Escape sequences to produce a terminal-output as close as possible to 158 | the graphical outputs. This makes debugging your config file a little bit 159 | easier because the terminal-output of i3status becomes much more readable, but 160 | should only used for such quick glances, because it will only support very 161 | basic output-features (for example you only get 3 bits of color depth). 162 | none:: 163 | Does not use any color codes. Separates values by the pipe symbol. This should 164 | be used with i3bar and can be used for custom scripts. 165 | 166 | It's also possible to use the color_good, color_degraded, color_bad directives 167 | to define specific colors per module. If one of these directives is defined 168 | in a module section its value will override the value defined in the general 169 | section just for this module. 170 | 171 | === IPv6 172 | 173 | This module gets the IPv6 address used for outgoing connections (that is, the 174 | best available public IPv6 address on your computer). 175 | 176 | *Example format_up*: +%ip+ 177 | 178 | *Example format_down*: +no IPv6+ 179 | 180 | === Disk 181 | 182 | Gets used, free, available and total amount of bytes on the given mounted filesystem. 183 | 184 | These values can also be expressed in percentages with the percentage_used, 185 | percentage_free, percentage_avail and percentage_used_of_avail formats. 186 | 187 | Byte sizes are presented in a human readable format using a set of prefixes 188 | whose type can be specified via the "prefix_type" option. Three sets of 189 | prefixes are available: 190 | 191 | binary:: 192 | IEC prefixes (Ki, Mi, Gi, Ti) represent multiples of powers of 1024. 193 | This is the default. 194 | decimal:: 195 | SI prefixes (k, M, G, T) represent multiples of powers of 1000. 196 | custom:: 197 | The custom prefixes (K, M, G, T) represent multiples of powers of 1024. 198 | 199 | *Example order*: +disk /mnt/usbstick+ 200 | 201 | *Example format*: +%free (%avail)/ %total+ 202 | 203 | *Example format*: +%percentage_used used, %percentage_free free, %percentage_avail avail+ 204 | 205 | *Example prefix_type*: +custom+ 206 | 207 | === Run-watch 208 | 209 | Expands the given path to a pidfile and checks if the process ID found inside 210 | is valid (that is, if the process is running). You can use this to check if 211 | a specific application, such as a VPN client or your DHCP client is running. 212 | 213 | *Example order*: +run_watch DHCP+ 214 | 215 | *Example format*: +%title: %status+ 216 | 217 | === Path-exists 218 | 219 | Checks if the given path exists in the filesystem. You can use this to check if 220 | something is active, like for example a VPN tunnel managed by NetworkManager. 221 | 222 | *Example order*: +path_exists VPN+ 223 | 224 | *Example format*: +%title: %status+ 225 | 226 | === Wireless 227 | 228 | Gets the link quality and ESSID of the given wireless network interface. You 229 | can specify different format strings for the network being connected or not 230 | connected. 231 | 232 | *Example order*: +wireless wlan0+ 233 | 234 | *Example format*: +W: (%quality at %essid, %bitrate) %ip+ 235 | 236 | === Ethernet 237 | 238 | Gets the IP address and (if possible) the link speed of the given ethernet 239 | interface. Getting the link speed requires the cap_net_admin capability. Set 240 | it using +setcap cap_net_admin=ep $(which i3status)+. 241 | 242 | *Example order*: +ethernet eth0+ 243 | 244 | *Example format*: +E: %ip (%speed)+ 245 | 246 | === Battery 247 | 248 | Gets the status (charging, discharging, running), percentage, remaining 249 | time and power consumption (in Watts) of the given battery and when it's 250 | estimated to be empty. If you want to use the last full capacity instead of the 251 | design capacity (when using the design capacity, it may happen that your 252 | battery is at 23% when fully charged because it’s old. In general, I want to 253 | see it this way, because it tells me how worn off my battery is.), just specify 254 | +last_full_capacity = true+. 255 | 256 | If you want the battery percentage to be shown without decimals, add 257 | +integer_battery_capacity = true+. 258 | 259 | If your battery is represented in a non-standard path in /sys, be sure to 260 | modify the "path" property accordingly, i.e. pointing to the uevent file on 261 | your system. The first occurence of %d gets replaced with the battery number, 262 | but you can just hard-code a path as well. 263 | 264 | It is possible to define a low_threshold that causes the battery text to be 265 | colored red. The low_threshold type can be of threshold_type "time" or 266 | "percentage". So, if you configure low_threshold to 10 and threshold_type to 267 | "time", and your battery lasts another 9 minutes, it will be colored red. 268 | 269 | Will also send libnotify notifications whenever battery status changes (when the 270 | computer is plugged in or unplugged, or when the battery becomes full), or 271 | whenever your battery crosses the configured low_threshold. The format of these 272 | notifications can be controlled with +notif_header_format+ and 273 | +notif_body_format+ 274 | 275 | *Example order*: +battery 0+ 276 | 277 | *Example format*: +%status %remaining (%emptytime %consumption)+ 278 | 279 | *Example format_down*: +No battery+ 280 | 281 | *Example low_threshold*: +30+ 282 | 283 | *Example threshold_type*: +time+ 284 | 285 | *Example path*: +/sys/class/power_supply/CMB1/uevent+ 286 | 287 | === CPU-Temperature 288 | 289 | Gets the temperature of the given thermal zone. It is possible to 290 | define a max_threshold that will color the temperature red in case the 291 | specified thermal zone is getting too hot. Defaults to 75 degrees C. 292 | 293 | *Example order*: +cpu_temperature 0+ 294 | 295 | *Example format*: +T: %degrees °C+ 296 | 297 | *Example max_threshold*: +42+ 298 | 299 | *Example path*: +/sys/devices/platform/coretemp.0/temp1_input+ 300 | 301 | === CPU Usage 302 | 303 | Gets the percentual CPU usage from +/proc/stat+ (Linux) or +sysctl(3)+ (FreeBSD/OpenBSD). 304 | 305 | *Example order*: +cpu_usage+ 306 | 307 | *Example format*: +%usage+ 308 | 309 | === Load 310 | 311 | Gets the system load (number of processes waiting for CPU time in the last 312 | 1, 5 and 15 minutes). It is possible to define a max_threshold that will 313 | color the load value red in case the load average of the last minute is 314 | getting higher than the configured threshold. Defaults to 5. 315 | 316 | *Example order*: +load+ 317 | 318 | *Example format*: +%1min %5min %15min+ 319 | 320 | *Example max_threshold*: +"0,1"+ 321 | 322 | === Time 323 | 324 | Outputs the current time in the local timezone. 325 | To use a different timezone, you can set the TZ environment variable, 326 | or use the +tztime+ module. 327 | See +strftime(3)+ for details on the format string. 328 | 329 | *Example order*: +time+ 330 | 331 | *Example format*: +%Y-%m-%d %H:%M:%S+ 332 | 333 | === TzTime 334 | 335 | Outputs the current time in the given timezone. 336 | If no timezone is given, local time will be used. 337 | See +strftime(3)+ for details on the format string. 338 | The system's timezone database is usually installed in +/usr/share/zoneinfo+. 339 | Files below that path make for valid timezone strings, e.g. for 340 | +/usr/share/zoneinfo/Europe/Berlin+ you can set timezone to +Europe/Berlin+ 341 | in the +tztime+ module. 342 | 343 | *Example order*: +tztime berlin+ 344 | 345 | *Example format*: +%Y-%m-%d %H:%M:%S %Z+ 346 | 347 | *Example timezone*: +Europe/Berlin+ 348 | 349 | === DDate 350 | 351 | Outputs the current discordian date in user-specified format. See +ddate(1)+ for 352 | details on the format string. 353 | *Note*: Neither *%.* nor *%X* are implemented yet. 354 | 355 | *Example order*: +ddate+ 356 | 357 | *Example format*: +%{%a, %b %d%}, %Y%N - %H+ 358 | 359 | === Volume 360 | 361 | Outputs the volume of the specified mixer on the specified device. Works only 362 | on Linux because it uses ALSA. 363 | A simplified configuration can be used on FreeBSD and OpenBSD due to 364 | the lack of ALSA, the +device+ and +mixer+ options can be 365 | ignored on these systems. On these systems the OSS API is used instead to 366 | query +/dev/mixer+ directly if +mixer_dix+ is -1, otherwise 367 | +/dev/mixer++mixer_idx+. 368 | 369 | *Example order*: +volume master+ 370 | 371 | *Example format*: +♪: %volume+ 372 | *Example format_muted*: +♪: 0%%+ 373 | 374 | *Example configuration*: 375 | ------------------------------------------------------------- 376 | volume master { 377 | format = "♪: %volume" 378 | format_muted = "♪: muted (%volume)" 379 | device = "default" 380 | mixer = "Master" 381 | mixer_idx = 0 382 | } 383 | ------------------------------------------------------------- 384 | 385 | === MPD 386 | 387 | Outputs the currently playing MPD track, using libmpdclient. Will display a 388 | libnotify notification whenever the track changes. 389 | 390 | *Example order*: +mpd+ 391 | 392 | *Example format*: +%artist - %album - %title+ 393 | 394 | *Example format_stopped*: +Stopped+ 395 | 396 | *Example notif_header_format*: +%title+ 397 | 398 | *Example notif_body_format*: +%artist - %album+ 399 | 400 | == Using i3status with dzen2 401 | 402 | After installing dzen2, you can directly use it with i3status. Just ensure that 403 | +output_format+ is set to +dzen2+. 404 | 405 | *Example for usage of i3status with dzen2*: 406 | -------------------------------------------------------------- 407 | i3status | dzen2 -fg white -ta r -w 1280 \ 408 | -fn "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1" 409 | -------------------------------------------------------------- 410 | 411 | == Using i3status with xmobar 412 | 413 | To get xmobar to start, you might need to copy the default configuration 414 | file to +~/.xmobarrc+. Also, ensure that the +output_format+ option for i3status 415 | is set to +xmobar+. 416 | 417 | *Example for usage of i3status with xmobar*: 418 | --------------------------------------------------------------------- 419 | i3status | xmobar -o -t "%StdinReader%" -c "[Run StdinReader]" 420 | --------------------------------------------------------------------- 421 | 422 | == What about memory usage or CPU frequency? 423 | 424 | While talking about two specific things, please understand this section as a 425 | general explanation why your favorite information is not included in i3status. 426 | 427 | Let’s talk about memory usage specifically. It is hard to measure memory in a 428 | way which is accurate or meaningful. An in-depth understanding of how paging 429 | and virtual memory work in your operating system is required. Furthermore, even 430 | if we had a well-defined way of displaying memory usage and you would 431 | understand it, I think that it’s not helpful to repeatedly monitor your memory 432 | usage. One reason for that is that I have not run out of memory in the last few 433 | years. Memory has become so cheap that even in my 4 year old notebook, I have 434 | 8 GiB of RAM. Another reason is that your operating system will do the right 435 | thing anyway: Either you have not enough RAM for your workload, but you need to 436 | do it anyway, then your operating system will swap. Or you don’t have enough 437 | RAM and you want to restrict your workload so that it fits, then the operating 438 | system will kill the process using too much RAM and you can act accordingly. 439 | 440 | For CPU frequency, the situation is similar. Many people don’t understand how 441 | frequency scaling works precisely. The generally recommended CPU frequency 442 | governor ("ondemand") changes the CPU frequency far more often than i3status 443 | could display it. The display number is therefore often incorrect and doesn’t 444 | tell you anything useful either. 445 | 446 | In general, i3status wants to display things which you would look at 447 | occasionally anyways, like the current date/time, whether you are connected to 448 | a WiFi network or not, and if you have enough disk space to fit that 4.3 GiB 449 | download. 450 | 451 | However, if you need to look at some kind of information more than once in a 452 | while (like checking repeatedly how full your RAM is), you are probably better 453 | off with a script doing that, which pops up an alert when your RAM usage reaches 454 | a certain threshold. After all, the point of computers is not to burden you 455 | with additional boring tasks like repeatedly checking a number. 456 | 457 | == External scripts/programs with i3status 458 | 459 | In i3status, we don’t want to implement process management again. Therefore, 460 | there is no module to run arbitrary scripts or commands. Instead, you should 461 | use your shell, for example like this: 462 | 463 | *Example for prepending the i3status output*: 464 | -------------------------------------------------------------- 465 | #!/bin/sh 466 | # shell script to prepend i3status with more stuff 467 | 468 | i3status | while : 469 | do 470 | read line 471 | echo "mystuff | $line" || exit 1 472 | done 473 | -------------------------------------------------------------- 474 | 475 | Put that in some script, say +.bin/my_i3status.sh+ and execute that instead of i3status. 476 | 477 | Note that if you want to use the JSON output format (with colors in i3bar), you 478 | need to use a slightly more complex wrapper script. There are examples in the 479 | contrib/ folder, see http://code.i3wm.org/i3status/tree/contrib 480 | 481 | == SIGNALS 482 | 483 | When receiving +SIGUSR1+, i3status’s nanosleep() will be interrupted and thus 484 | you will force an update. You can use killall -USR1 i3status to force an update 485 | after changing the system volume, for example. 486 | 487 | == SEE ALSO 488 | 489 | +strftime(3)+, +date(1)+, +glob(3)+, +dzen2(1)+, +xmobar(1)+ 490 | 491 | == AUTHORS 492 | 493 | Michael Stapelberg and contributors 494 | 495 | Thorsten Toepper 496 | 497 | Baptiste Daroussin 498 | 499 | Axel Wagner 500 | 501 | Fernando Tarlá Cardoso Lemos 502 | -------------------------------------------------------------------------------- /include/queue.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $ */ 2 | /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ 3 | 4 | /* 5 | * Copyright (c) 1991, 1993 6 | * The Regents of the University of California. All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. Neither the name of the University nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | * 32 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 33 | */ 34 | 35 | #ifndef _SYS_QUEUE_H_ 36 | #define _SYS_QUEUE_H_ 37 | 38 | /* 39 | * This file defines five types of data structures: singly-linked lists, 40 | * lists, simple queues, tail queues, and circular queues. 41 | * 42 | * 43 | * A singly-linked list is headed by a single forward pointer. The elements 44 | * are singly linked for minimum space and pointer manipulation overhead at 45 | * the expense of O(n) removal for arbitrary elements. New elements can be 46 | * added to the list after an existing element or at the head of the list. 47 | * Elements being removed from the head of the list should use the explicit 48 | * macro for this purpose for optimum efficiency. A singly-linked list may 49 | * only be traversed in the forward direction. Singly-linked lists are ideal 50 | * for applications with large datasets and few or no removals or for 51 | * implementing a LIFO queue. 52 | * 53 | * A list is headed by a single forward pointer (or an array of forward 54 | * pointers for a hash table header). The elements are doubly linked 55 | * so that an arbitrary element can be removed without a need to 56 | * traverse the list. New elements can be added to the list before 57 | * or after an existing element or at the head of the list. A list 58 | * may only be traversed in the forward direction. 59 | * 60 | * A simple queue is headed by a pair of pointers, one the head of the 61 | * list and the other to the tail of the list. The elements are singly 62 | * linked to save space, so elements can only be removed from the 63 | * head of the list. New elements can be added to the list before or after 64 | * an existing element, at the head of the list, or at the end of the 65 | * list. A simple queue may only be traversed in the forward direction. 66 | * 67 | * A tail queue is headed by a pair of pointers, one to the head of the 68 | * list and the other to the tail of the list. The elements are doubly 69 | * linked so that an arbitrary element can be removed without a need to 70 | * traverse the list. New elements can be added to the list before or 71 | * after an existing element, at the head of the list, or at the end of 72 | * the list. A tail queue may be traversed in either direction. 73 | * 74 | * A circle queue is headed by a pair of pointers, one to the head of the 75 | * list and the other to the tail of the list. The elements are doubly 76 | * linked so that an arbitrary element can be removed without a need to 77 | * traverse the list. New elements can be added to the list before or after 78 | * an existing element, at the head of the list, or at the end of the list. 79 | * A circle queue may be traversed in either direction, but has a more 80 | * complex end of list detection. 81 | * 82 | * For details on the use of these macros, see the queue(3) manual page. 83 | */ 84 | 85 | #if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) 86 | #define _Q_INVALIDATE(a) (a) = ((void *)-1) 87 | #else 88 | #define _Q_INVALIDATE(a) 89 | #endif 90 | 91 | /* 92 | * Singly-linked List definitions. 93 | */ 94 | #define SLIST_HEAD(name, type) \ 95 | struct name { \ 96 | struct type *slh_first; /* first element */ \ 97 | } 98 | 99 | #define SLIST_HEAD_INITIALIZER(head) \ 100 | { NULL } 101 | 102 | #define SLIST_ENTRY(type) \ 103 | struct { \ 104 | struct type *sle_next; /* next element */ \ 105 | } 106 | 107 | /* 108 | * Singly-linked List access methods. 109 | */ 110 | #define SLIST_FIRST(head) ((head)->slh_first) 111 | #define SLIST_END(head) NULL 112 | #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) 113 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 114 | 115 | #define SLIST_FOREACH(var, head, field) \ 116 | for((var) = SLIST_FIRST(head); \ 117 | (var) != SLIST_END(head); \ 118 | (var) = SLIST_NEXT(var, field)) 119 | 120 | #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ 121 | for ((varp) = &SLIST_FIRST((head)); \ 122 | ((var) = *(varp)) != SLIST_END(head); \ 123 | (varp) = &SLIST_NEXT((var), field)) 124 | 125 | /* 126 | * Singly-linked List functions. 127 | */ 128 | #define SLIST_INIT(head) { \ 129 | SLIST_FIRST(head) = SLIST_END(head); \ 130 | } 131 | 132 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 133 | (elm)->field.sle_next = (slistelm)->field.sle_next; \ 134 | (slistelm)->field.sle_next = (elm); \ 135 | } while (0) 136 | 137 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 138 | (elm)->field.sle_next = (head)->slh_first; \ 139 | (head)->slh_first = (elm); \ 140 | } while (0) 141 | 142 | #define SLIST_REMOVE_NEXT(head, elm, field) do { \ 143 | (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ 144 | } while (0) 145 | 146 | #define SLIST_REMOVE_HEAD(head, field) do { \ 147 | (head)->slh_first = (head)->slh_first->field.sle_next; \ 148 | } while (0) 149 | 150 | #define SLIST_REMOVE(head, elm, type, field) do { \ 151 | if ((head)->slh_first == (elm)) { \ 152 | SLIST_REMOVE_HEAD((head), field); \ 153 | } else { \ 154 | struct type *curelm = (head)->slh_first; \ 155 | \ 156 | while (curelm->field.sle_next != (elm)) \ 157 | curelm = curelm->field.sle_next; \ 158 | curelm->field.sle_next = \ 159 | curelm->field.sle_next->field.sle_next; \ 160 | _Q_INVALIDATE((elm)->field.sle_next); \ 161 | } \ 162 | } while (0) 163 | 164 | /* 165 | * List definitions. 166 | */ 167 | #define LIST_HEAD(name, type) \ 168 | struct name { \ 169 | struct type *lh_first; /* first element */ \ 170 | } 171 | 172 | #define LIST_HEAD_INITIALIZER(head) \ 173 | { NULL } 174 | 175 | #define LIST_ENTRY(type) \ 176 | struct { \ 177 | struct type *le_next; /* next element */ \ 178 | struct type **le_prev; /* address of previous next element */ \ 179 | } 180 | 181 | /* 182 | * List access methods 183 | */ 184 | #define LIST_FIRST(head) ((head)->lh_first) 185 | #define LIST_END(head) NULL 186 | #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) 187 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 188 | 189 | #define LIST_FOREACH(var, head, field) \ 190 | for((var) = LIST_FIRST(head); \ 191 | (var)!= LIST_END(head); \ 192 | (var) = LIST_NEXT(var, field)) 193 | 194 | /* 195 | * List functions. 196 | */ 197 | #define LIST_INIT(head) do { \ 198 | LIST_FIRST(head) = LIST_END(head); \ 199 | } while (0) 200 | 201 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 202 | if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ 203 | (listelm)->field.le_next->field.le_prev = \ 204 | &(elm)->field.le_next; \ 205 | (listelm)->field.le_next = (elm); \ 206 | (elm)->field.le_prev = &(listelm)->field.le_next; \ 207 | } while (0) 208 | 209 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 210 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 211 | (elm)->field.le_next = (listelm); \ 212 | *(listelm)->field.le_prev = (elm); \ 213 | (listelm)->field.le_prev = &(elm)->field.le_next; \ 214 | } while (0) 215 | 216 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 217 | if (((elm)->field.le_next = (head)->lh_first) != NULL) \ 218 | (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ 219 | (head)->lh_first = (elm); \ 220 | (elm)->field.le_prev = &(head)->lh_first; \ 221 | } while (0) 222 | 223 | #define LIST_REMOVE(elm, field) do { \ 224 | if ((elm)->field.le_next != NULL) \ 225 | (elm)->field.le_next->field.le_prev = \ 226 | (elm)->field.le_prev; \ 227 | *(elm)->field.le_prev = (elm)->field.le_next; \ 228 | _Q_INVALIDATE((elm)->field.le_prev); \ 229 | _Q_INVALIDATE((elm)->field.le_next); \ 230 | } while (0) 231 | 232 | #define LIST_REPLACE(elm, elm2, field) do { \ 233 | if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ 234 | (elm2)->field.le_next->field.le_prev = \ 235 | &(elm2)->field.le_next; \ 236 | (elm2)->field.le_prev = (elm)->field.le_prev; \ 237 | *(elm2)->field.le_prev = (elm2); \ 238 | _Q_INVALIDATE((elm)->field.le_prev); \ 239 | _Q_INVALIDATE((elm)->field.le_next); \ 240 | } while (0) 241 | 242 | /* 243 | * Simple queue definitions. 244 | */ 245 | #define SIMPLEQ_HEAD(name, type) \ 246 | struct name { \ 247 | struct type *sqh_first; /* first element */ \ 248 | struct type **sqh_last; /* addr of last next element */ \ 249 | } 250 | 251 | #define SIMPLEQ_HEAD_INITIALIZER(head) \ 252 | { NULL, &(head).sqh_first } 253 | 254 | #define SIMPLEQ_ENTRY(type) \ 255 | struct { \ 256 | struct type *sqe_next; /* next element */ \ 257 | } 258 | 259 | /* 260 | * Simple queue access methods. 261 | */ 262 | #define SIMPLEQ_FIRST(head) ((head)->sqh_first) 263 | #define SIMPLEQ_END(head) NULL 264 | #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) 265 | #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) 266 | 267 | #define SIMPLEQ_FOREACH(var, head, field) \ 268 | for((var) = SIMPLEQ_FIRST(head); \ 269 | (var) != SIMPLEQ_END(head); \ 270 | (var) = SIMPLEQ_NEXT(var, field)) 271 | 272 | /* 273 | * Simple queue functions. 274 | */ 275 | #define SIMPLEQ_INIT(head) do { \ 276 | (head)->sqh_first = NULL; \ 277 | (head)->sqh_last = &(head)->sqh_first; \ 278 | } while (0) 279 | 280 | #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ 281 | if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ 282 | (head)->sqh_last = &(elm)->field.sqe_next; \ 283 | (head)->sqh_first = (elm); \ 284 | } while (0) 285 | 286 | #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ 287 | (elm)->field.sqe_next = NULL; \ 288 | *(head)->sqh_last = (elm); \ 289 | (head)->sqh_last = &(elm)->field.sqe_next; \ 290 | } while (0) 291 | 292 | #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 293 | if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ 294 | (head)->sqh_last = &(elm)->field.sqe_next; \ 295 | (listelm)->field.sqe_next = (elm); \ 296 | } while (0) 297 | 298 | #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ 299 | if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ 300 | (head)->sqh_last = &(head)->sqh_first; \ 301 | } while (0) 302 | 303 | /* 304 | * Tail queue definitions. 305 | */ 306 | #define TAILQ_HEAD(name, type) \ 307 | struct name { \ 308 | struct type *tqh_first; /* first element */ \ 309 | struct type **tqh_last; /* addr of last next element */ \ 310 | } 311 | 312 | #define TAILQ_HEAD_INITIALIZER(head) \ 313 | { NULL, &(head).tqh_first } 314 | 315 | #define TAILQ_ENTRY(type) \ 316 | struct { \ 317 | struct type *tqe_next; /* next element */ \ 318 | struct type **tqe_prev; /* address of previous next element */ \ 319 | } 320 | 321 | /* 322 | * tail queue access methods 323 | */ 324 | #define TAILQ_FIRST(head) ((head)->tqh_first) 325 | #define TAILQ_END(head) NULL 326 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 327 | #define TAILQ_LAST(head, headname) \ 328 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 329 | /* XXX */ 330 | #define TAILQ_PREV(elm, headname, field) \ 331 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 332 | #define TAILQ_EMPTY(head) \ 333 | (TAILQ_FIRST(head) == TAILQ_END(head)) 334 | 335 | #define TAILQ_FOREACH(var, head, field) \ 336 | for((var) = TAILQ_FIRST(head); \ 337 | (var) != TAILQ_END(head); \ 338 | (var) = TAILQ_NEXT(var, field)) 339 | 340 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 341 | for((var) = TAILQ_LAST(head, headname); \ 342 | (var) != TAILQ_END(head); \ 343 | (var) = TAILQ_PREV(var, headname, field)) 344 | 345 | /* 346 | * Tail queue functions. 347 | */ 348 | #define TAILQ_INIT(head) do { \ 349 | (head)->tqh_first = NULL; \ 350 | (head)->tqh_last = &(head)->tqh_first; \ 351 | } while (0) 352 | 353 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 354 | if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ 355 | (head)->tqh_first->field.tqe_prev = \ 356 | &(elm)->field.tqe_next; \ 357 | else \ 358 | (head)->tqh_last = &(elm)->field.tqe_next; \ 359 | (head)->tqh_first = (elm); \ 360 | (elm)->field.tqe_prev = &(head)->tqh_first; \ 361 | } while (0) 362 | 363 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 364 | (elm)->field.tqe_next = NULL; \ 365 | (elm)->field.tqe_prev = (head)->tqh_last; \ 366 | *(head)->tqh_last = (elm); \ 367 | (head)->tqh_last = &(elm)->field.tqe_next; \ 368 | } while (0) 369 | 370 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 371 | if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ 372 | (elm)->field.tqe_next->field.tqe_prev = \ 373 | &(elm)->field.tqe_next; \ 374 | else \ 375 | (head)->tqh_last = &(elm)->field.tqe_next; \ 376 | (listelm)->field.tqe_next = (elm); \ 377 | (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ 378 | } while (0) 379 | 380 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 381 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 382 | (elm)->field.tqe_next = (listelm); \ 383 | *(listelm)->field.tqe_prev = (elm); \ 384 | (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ 385 | } while (0) 386 | 387 | #define TAILQ_REMOVE(head, elm, field) do { \ 388 | if (((elm)->field.tqe_next) != NULL) \ 389 | (elm)->field.tqe_next->field.tqe_prev = \ 390 | (elm)->field.tqe_prev; \ 391 | else \ 392 | (head)->tqh_last = (elm)->field.tqe_prev; \ 393 | *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ 394 | _Q_INVALIDATE((elm)->field.tqe_prev); \ 395 | _Q_INVALIDATE((elm)->field.tqe_next); \ 396 | } while (0) 397 | 398 | #define TAILQ_REPLACE(head, elm, elm2, field) do { \ 399 | if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ 400 | (elm2)->field.tqe_next->field.tqe_prev = \ 401 | &(elm2)->field.tqe_next; \ 402 | else \ 403 | (head)->tqh_last = &(elm2)->field.tqe_next; \ 404 | (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ 405 | *(elm2)->field.tqe_prev = (elm2); \ 406 | _Q_INVALIDATE((elm)->field.tqe_prev); \ 407 | _Q_INVALIDATE((elm)->field.tqe_next); \ 408 | } while (0) 409 | 410 | /* 411 | * Circular queue definitions. 412 | */ 413 | #define CIRCLEQ_HEAD(name, type) \ 414 | struct name { \ 415 | struct type *cqh_first; /* first element */ \ 416 | struct type *cqh_last; /* last element */ \ 417 | } 418 | 419 | #define CIRCLEQ_HEAD_INITIALIZER(head) \ 420 | { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } 421 | 422 | #define CIRCLEQ_ENTRY(type) \ 423 | struct { \ 424 | struct type *cqe_next; /* next element */ \ 425 | struct type *cqe_prev; /* previous element */ \ 426 | } 427 | 428 | /* 429 | * Circular queue access methods 430 | */ 431 | #define CIRCLEQ_FIRST(head) ((head)->cqh_first) 432 | #define CIRCLEQ_LAST(head) ((head)->cqh_last) 433 | #define CIRCLEQ_END(head) ((void *)(head)) 434 | #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) 435 | #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) 436 | #define CIRCLEQ_EMPTY(head) \ 437 | (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) 438 | 439 | #define CIRCLEQ_FOREACH(var, head, field) \ 440 | for((var) = CIRCLEQ_FIRST(head); \ 441 | (var) != CIRCLEQ_END(head); \ 442 | (var) = CIRCLEQ_NEXT(var, field)) 443 | 444 | #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ 445 | for((var) = CIRCLEQ_LAST(head); \ 446 | (var) != CIRCLEQ_END(head); \ 447 | (var) = CIRCLEQ_PREV(var, field)) 448 | 449 | /* 450 | * Circular queue functions. 451 | */ 452 | #define CIRCLEQ_INIT(head) do { \ 453 | (head)->cqh_first = CIRCLEQ_END(head); \ 454 | (head)->cqh_last = CIRCLEQ_END(head); \ 455 | } while (0) 456 | 457 | #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 458 | (elm)->field.cqe_next = (listelm)->field.cqe_next; \ 459 | (elm)->field.cqe_prev = (listelm); \ 460 | if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ 461 | (head)->cqh_last = (elm); \ 462 | else \ 463 | (listelm)->field.cqe_next->field.cqe_prev = (elm); \ 464 | (listelm)->field.cqe_next = (elm); \ 465 | } while (0) 466 | 467 | #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ 468 | (elm)->field.cqe_next = (listelm); \ 469 | (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ 470 | if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ 471 | (head)->cqh_first = (elm); \ 472 | else \ 473 | (listelm)->field.cqe_prev->field.cqe_next = (elm); \ 474 | (listelm)->field.cqe_prev = (elm); \ 475 | } while (0) 476 | 477 | #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ 478 | (elm)->field.cqe_next = (head)->cqh_first; \ 479 | (elm)->field.cqe_prev = CIRCLEQ_END(head); \ 480 | if ((head)->cqh_last == CIRCLEQ_END(head)) \ 481 | (head)->cqh_last = (elm); \ 482 | else \ 483 | (head)->cqh_first->field.cqe_prev = (elm); \ 484 | (head)->cqh_first = (elm); \ 485 | } while (0) 486 | 487 | #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ 488 | (elm)->field.cqe_next = CIRCLEQ_END(head); \ 489 | (elm)->field.cqe_prev = (head)->cqh_last; \ 490 | if ((head)->cqh_first == CIRCLEQ_END(head)) \ 491 | (head)->cqh_first = (elm); \ 492 | else \ 493 | (head)->cqh_last->field.cqe_next = (elm); \ 494 | (head)->cqh_last = (elm); \ 495 | } while (0) 496 | 497 | #define CIRCLEQ_REMOVE(head, elm, field) do { \ 498 | if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ 499 | (head)->cqh_last = (elm)->field.cqe_prev; \ 500 | else \ 501 | (elm)->field.cqe_next->field.cqe_prev = \ 502 | (elm)->field.cqe_prev; \ 503 | if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ 504 | (head)->cqh_first = (elm)->field.cqe_next; \ 505 | else \ 506 | (elm)->field.cqe_prev->field.cqe_next = \ 507 | (elm)->field.cqe_next; \ 508 | _Q_INVALIDATE((elm)->field.cqe_prev); \ 509 | _Q_INVALIDATE((elm)->field.cqe_next); \ 510 | } while (0) 511 | 512 | #define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ 513 | if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ 514 | CIRCLEQ_END(head)) \ 515 | (head)->cqh_last = (elm2); \ 516 | else \ 517 | (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ 518 | if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ 519 | CIRCLEQ_END(head)) \ 520 | (head)->cqh_first = (elm2); \ 521 | else \ 522 | (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ 523 | _Q_INVALIDATE((elm)->field.cqe_prev); \ 524 | _Q_INVALIDATE((elm)->field.cqe_next); \ 525 | } while (0) 526 | 527 | #endif /* !_SYS_QUEUE_H_ */ 528 | -------------------------------------------------------------------------------- /src/print_battery_info.c: -------------------------------------------------------------------------------- 1 | // vim:ts=8:expandtab 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "i3status.h" 14 | 15 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 16 | #include 17 | #include 18 | #endif 19 | 20 | #if defined(__OpenBSD__) 21 | #include 22 | #include 23 | #include 24 | #include 25 | #endif 26 | 27 | static char *prev_status; 28 | static bool prev_critical = false; 29 | 30 | struct battery_info { 31 | const char *status; 32 | const char *percentage; 33 | const char *remaining; 34 | const char *emptytime; 35 | const char *consumption; 36 | bool critical; 37 | }; 38 | 39 | struct battery_info battery_info_new( 40 | const char *status, 41 | const char *percentage, 42 | const char *remaining, 43 | const char *emptytime, 44 | const char *consumption, 45 | bool critical 46 | ) { 47 | struct battery_info info; 48 | 49 | info.status = status; 50 | info.percentage = percentage; 51 | info.remaining = remaining; 52 | info.emptytime = emptytime; 53 | info.consumption = consumption; 54 | info.critical = critical; 55 | 56 | return info; 57 | } 58 | 59 | #define BATTERY_OUTPUT_OPTION(option_name, property) \ 60 | else if (BEGINS_WITH(walk + 1, option_name)) { \ 61 | outwalk += sprintf(outwalk, "%s", info.property); \ 62 | walk += strlen(option_name); \ 63 | } 64 | 65 | void battery_format_string( 66 | const struct battery_info info, 67 | const char *format, 68 | char **output 69 | ) { 70 | char *outwalk = *output; 71 | 72 | const char *walk; 73 | 74 | for (walk = format; *walk != '\0'; walk++) { 75 | if (*walk != '%') { 76 | *(outwalk++) = *walk; 77 | continue; 78 | } 79 | 80 | if (false) {} 81 | BATTERY_OUTPUT_OPTION("status", status) 82 | BATTERY_OUTPUT_OPTION("percentage", percentage) 83 | BATTERY_OUTPUT_OPTION("remaining", remaining) 84 | BATTERY_OUTPUT_OPTION("emptytime", emptytime) 85 | BATTERY_OUTPUT_OPTION("consumption", consumption) 86 | } 87 | 88 | *(outwalk++) = '\0'; 89 | } 90 | 91 | void battery_send_notification( 92 | const struct battery_info info, 93 | const char *header_format, 94 | const char *body_format 95 | ) { 96 | char *outwalk; 97 | char header[4096]; 98 | char body[4096]; 99 | 100 | outwalk = header; 101 | battery_format_string(info, header_format, &outwalk); 102 | 103 | outwalk = body; 104 | battery_format_string(info, body_format, &outwalk); 105 | 106 | NotifyNotification *battery_notification = notify_notification_new(header, body, "dialog-information"); 107 | 108 | if (info.critical == true) { 109 | notify_notification_set_urgency(battery_notification, NOTIFY_URGENCY_CRITICAL); 110 | } 111 | 112 | notify_notification_show(battery_notification, NULL); 113 | g_object_unref(G_OBJECT(battery_notification)); 114 | } 115 | 116 | #define BATT_STATUS_NAME(status) \ 117 | (status == CS_CHARGING ? "CHR" : \ 118 | (status == CS_DISCHARGING ? "BAT" : "FULL")) 119 | 120 | /* 121 | * Get battery information from /sys. Note that it uses the design capacity to 122 | * calculate the percentage, not the last full capacity, so you can see how 123 | * worn off your battery is. 124 | * 125 | */ 126 | void print_battery_info( 127 | yajl_gen json_gen, 128 | char *buffer, 129 | int number, 130 | const char *path, 131 | const char *format, 132 | const char *format_down, 133 | const char *notif_header_format, 134 | const char *notif_body_format, 135 | int low_threshold, 136 | char *threshold_type, 137 | bool last_full_capacity, 138 | bool integer_battery_capacity 139 | ) { 140 | time_t empty_time; 141 | struct tm *empty_tm; 142 | 143 | char buf[1024]; 144 | char statusbuf[16]; 145 | char percentagebuf[16]; 146 | char remainingbuf[256]; 147 | char emptytimebuf[256]; 148 | char consumptionbuf[256]; 149 | bool critical = false; 150 | 151 | const char *walk, *last; 152 | char *outwalk = buffer; 153 | bool watt_as_unit; 154 | bool colorful_output = false; 155 | int full_design = -1, 156 | remaining = -1, 157 | present_rate = -1, 158 | voltage = -1; 159 | charging_status_t status = CS_DISCHARGING; 160 | 161 | memset(statusbuf, '\0', sizeof(statusbuf)); 162 | memset(percentagebuf, '\0', sizeof(percentagebuf)); 163 | memset(remainingbuf, '\0', sizeof(remainingbuf)); 164 | memset(emptytimebuf, '\0', sizeof(emptytimebuf)); 165 | memset(consumptionbuf, '\0', sizeof(consumptionbuf)); 166 | 167 | static char batpath[512]; 168 | sprintf(batpath, path, number); 169 | INSTANCE(batpath); 170 | 171 | #if defined(LINUX) 172 | if (!slurp(batpath, buf, sizeof(buf))) { 173 | OUTPUT_FULL_TEXT(format_down); 174 | return; 175 | } 176 | 177 | for (walk = buf, last = buf; (walk-buf) < 1024; walk++) { 178 | if (*walk == '\n') { 179 | last = walk+1; 180 | continue; 181 | } 182 | 183 | if (*walk != '=') 184 | continue; 185 | 186 | if (BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_NOW")) { 187 | watt_as_unit = true; 188 | remaining = atoi(walk+1); 189 | } 190 | else if (BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_NOW")) { 191 | watt_as_unit = false; 192 | remaining = atoi(walk+1); 193 | } 194 | else if (BEGINS_WITH(last, "POWER_SUPPLY_CURRENT_NOW")) 195 | present_rate = atoi(walk+1); 196 | else if (BEGINS_WITH(last, "POWER_SUPPLY_VOLTAGE_NOW")) 197 | voltage = atoi(walk+1); 198 | /* on some systems POWER_SUPPLY_POWER_NOW does not exist, but actually 199 | * it is the same as POWER_SUPPLY_CURRENT_NOW but with μWh as 200 | * unit instead of μAh. We will calculate it as we need it 201 | * later. */ 202 | else if (BEGINS_WITH(last, "POWER_SUPPLY_POWER_NOW")) 203 | present_rate = atoi(walk+1); 204 | else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Charging")) 205 | status = CS_CHARGING; 206 | else if (BEGINS_WITH(last, "POWER_SUPPLY_STATUS=Full")) 207 | status = CS_FULL; 208 | else { 209 | /* The only thing left is the full capacity */ 210 | if (last_full_capacity) { 211 | if (!BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL") && 212 | !BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL")) 213 | continue; 214 | } else { 215 | if (!BEGINS_WITH(last, "POWER_SUPPLY_CHARGE_FULL_DESIGN") && 216 | !BEGINS_WITH(last, "POWER_SUPPLY_ENERGY_FULL_DESIGN")) 217 | continue; 218 | } 219 | 220 | full_design = atoi(walk+1); 221 | } 222 | } 223 | 224 | /* the difference between POWER_SUPPLY_ENERGY_NOW and 225 | * POWER_SUPPLY_CHARGE_NOW is the unit of measurement. The energy is 226 | * given in mWh, the charge in mAh. So calculate every value given in 227 | * ampere to watt */ 228 | if (!watt_as_unit) { 229 | present_rate = (((float)voltage / 1000.0) * ((float)present_rate / 1000.0)); 230 | remaining = (((float)voltage / 1000.0) * ((float)remaining / 1000.0)); 231 | full_design = (((float)voltage / 1000.0) * ((float)full_design / 1000.0)); 232 | } 233 | 234 | if ((full_design == -1) || (remaining == -1)) { 235 | OUTPUT_FULL_TEXT(format_down); 236 | return; 237 | } 238 | 239 | (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status)); 240 | 241 | float percentage_remaining = (((float)remaining / (float)full_design) * 100); 242 | if (integer_battery_capacity) { 243 | (void)snprintf(percentagebuf, sizeof(percentagebuf), "%.00f%%", percentage_remaining); 244 | } else { 245 | (void)snprintf(percentagebuf, sizeof(percentagebuf), "%.02f%%", percentage_remaining); 246 | } 247 | 248 | if (present_rate > 0) { 249 | float remaining_time; 250 | int seconds, hours, minutes, seconds_remaining; 251 | if (status == CS_CHARGING) 252 | remaining_time = ((float)full_design - (float)remaining) / (float)present_rate; 253 | else if (status == CS_DISCHARGING) 254 | remaining_time = ((float)remaining / (float)present_rate); 255 | else remaining_time = 0; 256 | 257 | seconds_remaining = (int)(remaining_time * 3600.0); 258 | 259 | hours = seconds_remaining / 3600; 260 | seconds = seconds_remaining - (hours * 3600); 261 | minutes = seconds / 60; 262 | seconds -= (minutes * 60); 263 | 264 | if (status == CS_DISCHARGING && low_threshold > 0) { 265 | if ((strncmp(threshold_type, "percentage", strlen(threshold_type)) == 0 && 266 | percentage_remaining < low_threshold 267 | ) || (strncmp(threshold_type, "time", strlen(threshold_type)) == 0 && 268 | seconds_remaining < 60 * low_threshold 269 | )) { 270 | START_COLOR("color_bad"); 271 | colorful_output = true; 272 | critical = true; 273 | } else { 274 | colorful_output = false; 275 | } 276 | } 277 | 278 | (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02d:%02d:%02d", 279 | max(hours, 0), max(minutes, 0), max(seconds, 0)); 280 | 281 | empty_time = time(NULL); 282 | empty_time += seconds_remaining; 283 | empty_tm = localtime(&empty_time); 284 | 285 | (void)snprintf(emptytimebuf, sizeof(emptytimebuf), "%02d:%02d:%02d", 286 | max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0)); 287 | 288 | (void)snprintf(consumptionbuf, sizeof(consumptionbuf), "%1.2fW", 289 | ((float)present_rate / 1000.0 / 1000.0)); 290 | } else { 291 | /* On some systems, present_rate may not exist. Still, make sure 292 | * we colorize the output if threshold_type is set to percentage 293 | * (since we don't have any information on remaining time). */ 294 | if (status == CS_DISCHARGING && low_threshold > 0) { 295 | if (strncmp(threshold_type, "percentage", strlen(threshold_type)) == 0 296 | && percentage_remaining < low_threshold) { 297 | START_COLOR("color_bad"); 298 | colorful_output = true; 299 | critical = true; 300 | } 301 | } 302 | } 303 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) 304 | int state; 305 | int sysctl_rslt; 306 | size_t sysctl_size = sizeof(sysctl_rslt); 307 | 308 | if (sysctlbyname(BATT_LIFE, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) { 309 | OUTPUT_FULL_TEXT(format_down); 310 | return; 311 | } 312 | 313 | present_rate = sysctl_rslt; 314 | if (sysctlbyname(BATT_TIME, &sysctl_rslt, &sysctl_size, NULL, 0) != 0) { 315 | OUTPUT_FULL_TEXT(format_down); 316 | return; 317 | } 318 | 319 | remaining = sysctl_rslt; 320 | if (sysctlbyname(BATT_STATE, &sysctl_rslt, &sysctl_size, NULL,0) != 0) { 321 | OUTPUT_FULL_TEXT(format_down); 322 | return; 323 | } 324 | 325 | state = sysctl_rslt; 326 | if (state == 0 && present_rate == 100) 327 | status = CS_FULL; 328 | else if (state == 0 && present_rate < 100) 329 | status = CS_CHARGING; 330 | else 331 | status = CS_DISCHARGING; 332 | 333 | full_design = sysctl_rslt; 334 | 335 | (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status)); 336 | 337 | (void)snprintf(percentagebuf, sizeof(percentagebuf), "%02d%%", 338 | present_rate); 339 | 340 | if (state == 1) { 341 | int hours, minutes; 342 | minutes = remaining; 343 | hours = minutes / 60; 344 | minutes -= (hours * 60); 345 | (void)snprintf(remainingbuf, sizeof(remainingbuf), "%02dh%02d", 346 | max(hours, 0), max(minutes, 0)); 347 | if (strncmp(threshold_type, "percentage", strlen(threshold_type)) == 0 348 | && present_rate < low_threshold) { 349 | START_COLOR("color_bad"); 350 | colorful_output = true; 351 | critical = true; 352 | } else if (strncmp(threshold_type, "time", strlen(threshold_type)) == 0 353 | && remaining < (u_int) low_threshold) { 354 | START_COLOR("color_bad"); 355 | colorful_output = true; 356 | critical = true; 357 | } 358 | } 359 | #elif defined(__OpenBSD__) 360 | /* 361 | * We're using apm(4) here, which is the interface to acpi(4) on amd64/i386 and 362 | * the generic interface on macppc/sparc64/zaurus, instead of using sysctl(3) and 363 | * probing acpi(4) devices. 364 | */ 365 | struct apm_power_info apm_info; 366 | int apm_fd; 367 | 368 | apm_fd = open("/dev/apm", O_RDONLY); 369 | if (apm_fd < 0) { 370 | OUTPUT_FULL_TEXT("can't open /dev/apm"); 371 | return; 372 | } 373 | if (ioctl(apm_fd, APM_IOC_GETPOWER, &apm_info) < 0) 374 | OUTPUT_FULL_TEXT("can't read power info"); 375 | 376 | close(apm_fd); 377 | 378 | /* Don't bother to go further if there's no battery present. */ 379 | if ((apm_info.battery_state == APM_BATTERY_ABSENT) || 380 | (apm_info.battery_state == APM_BATT_UNKNOWN)) { 381 | OUTPUT_FULL_TEXT(format_down); 382 | return; 383 | } 384 | 385 | switch(apm_info.ac_state) { 386 | case APM_AC_OFF: 387 | status = CS_DISCHARGING; 388 | break; 389 | case APM_AC_ON: 390 | status = CS_CHARGING; 391 | break; 392 | default: 393 | /* If we don't know what's going on, just assume we're discharging. */ 394 | status = CS_DISCHARGING; 395 | break; 396 | } 397 | 398 | (void)snprintf(statusbuf, sizeof(statusbuf), "%s", BATT_STATUS_NAME(status)); 399 | (void)snprintf(percentagebuf, sizeof(percentagebuf), "%02d%%", apm_info.battery_life); 400 | 401 | if (status == CS_DISCHARGING && low_threshold > 0) { 402 | if (strncmp(threshold_type, "percentage", strlen(threshold_type)) == 0 403 | && apm_info.battery_life < low_threshold) { 404 | START_COLOR("color_bad"); 405 | colorful_output = true; 406 | critical = true; 407 | } else if (strncmp(threshold_type, "time", strlen(threshold_type)) == 0 408 | && apm_info.minutes_left < (u_int) low_threshold) { 409 | START_COLOR("color_bad"); 410 | colorful_output = true; 411 | critical = true; 412 | } 413 | } 414 | 415 | /* Can't give a meaningful value for remaining minutes if we're charging. */ 416 | if (status != CS_CHARGING) { 417 | (void)snprintf(remainingbuf, sizeof(remainingbuf), "%d", apm_info.minutes_left); 418 | } else { 419 | (void)snprintf(remainingbuf, sizeof(remainingbuf), "%s", "(CHR)"); 420 | } 421 | 422 | if (colorful_output) { 423 | END_COLOR; 424 | } 425 | #endif 426 | 427 | struct battery_info info = battery_info_new( 428 | statusbuf, 429 | percentagebuf, 430 | remainingbuf, 431 | emptytimebuf, 432 | consumptionbuf, 433 | critical 434 | ); 435 | 436 | #define EAT_SPACE_FROM_OUTPUT_IF_EMPTY(_buf) \ 437 | do { \ 438 | if (strlen(_buf) == 0) { \ 439 | if (outwalk > buffer && isspace(outwalk[-1])) { \ 440 | outwalk--; \ 441 | } else if (isspace(*(walk+1))) {\ 442 | walk++; \ 443 | } \ 444 | } \ 445 | } while (0) 446 | 447 | for (walk = format; *walk != '\0'; walk++) { 448 | if (*walk != '%') { 449 | *(outwalk++) = *walk; 450 | continue; 451 | } 452 | 453 | if (BEGINS_WITH(walk + 1, "status")) { 454 | outwalk += sprintf(outwalk, "%s", statusbuf); 455 | walk += strlen("status"); 456 | } else if (BEGINS_WITH(walk + 1, "percentage")) { 457 | outwalk += sprintf(outwalk, "%s", percentagebuf); 458 | walk += strlen("percentage"); 459 | } else if (BEGINS_WITH(walk + 1, "remaining")) { 460 | outwalk += sprintf(outwalk, "%s", remainingbuf); 461 | walk += strlen("remaining"); 462 | EAT_SPACE_FROM_OUTPUT_IF_EMPTY(remainingbuf); 463 | } else if (BEGINS_WITH(walk + 1, "emptytime")) { 464 | outwalk += sprintf(outwalk, "%s", emptytimebuf); 465 | walk += strlen("emptytime"); 466 | EAT_SPACE_FROM_OUTPUT_IF_EMPTY(emptytimebuf); 467 | } else if (BEGINS_WITH(walk + 1, "consumption")) { 468 | outwalk += sprintf(outwalk, "%s", consumptionbuf); 469 | walk += strlen("consumption"); 470 | EAT_SPACE_FROM_OUTPUT_IF_EMPTY(consumptionbuf); 471 | } 472 | } 473 | 474 | if (prev_status != NULL && strcmp(prev_status, info.status) == 0 && 475 | prev_critical == critical) { 476 | goto end; 477 | } 478 | 479 | battery_send_notification(info, notif_header_format, notif_body_format); 480 | 481 | free(prev_status); 482 | if ((prev_status = (char *)malloc(sizeof(char) * (strlen(info.status) + 1))) == NULL) { 483 | goto end; 484 | } 485 | strcpy(prev_status, info.status); 486 | prev_critical = critical; 487 | 488 | end: 489 | if (colorful_output) { END_COLOR; } 490 | OUTPUT_FULL_TEXT(buffer); 491 | } 492 | -------------------------------------------------------------------------------- /man/i3status.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: i3status 3 | .\" Author: [see the "AUTHORS" section] 4 | .\" Generator: DocBook XSL Stylesheets v1.78.1 5 | .\" Date: 03/19/2015 6 | .\" Manual: i3 Manual 7 | .\" Source: i3status v2.8 8 | .\" Language: English 9 | .\" 10 | .TH "I3STATUS" "1" "03/19/2015" "i3status v2\&.8" "i3 Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | i3status \- Generates a status line for i3bar, dzen2 or xmobar 32 | .SH "SYNOPSIS" 33 | .sp 34 | i3status [\-c configfile] [\-h] [\-v] 35 | .SH "OPTIONS" 36 | .PP 37 | \-c 38 | .RS 4 39 | Specifies an alternate configuration file path\&. By default, i3status looks for configuration files in the following order: 40 | .sp 41 | .RS 4 42 | .ie n \{\ 43 | \h'-04' 1.\h'+01'\c 44 | .\} 45 | .el \{\ 46 | .sp -1 47 | .IP " 1." 4.2 48 | .\} 49 | ~/\&.i3status\&.conf 50 | .RE 51 | .sp 52 | .RS 4 53 | .ie n \{\ 54 | \h'-04' 2.\h'+01'\c 55 | .\} 56 | .el \{\ 57 | .sp -1 58 | .IP " 2." 4.2 59 | .\} 60 | ~/\&.config/i3status/config (or $XDG_CONFIG_HOME/i3status/config if set) 61 | .RE 62 | .sp 63 | .RS 4 64 | .ie n \{\ 65 | \h'-04' 3.\h'+01'\c 66 | .\} 67 | .el \{\ 68 | .sp -1 69 | .IP " 3." 4.2 70 | .\} 71 | /etc/i3status\&.conf 72 | .RE 73 | .sp 74 | .RS 4 75 | .ie n \{\ 76 | \h'-04' 4.\h'+01'\c 77 | .\} 78 | .el \{\ 79 | .sp -1 80 | .IP " 4." 4.2 81 | .\} 82 | /etc/xdg/i3status/config (or $XDG_CONFIG_DIRS/i3status/config if set) 83 | .RE 84 | .RE 85 | .SH "DESCRIPTION" 86 | .sp 87 | i3status is a small program (about 1500 SLOC) for generating a status bar for i3bar, dzen2, xmobar 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\&. 88 | .SH "CONFIGURATION" 89 | .sp 90 | The basic idea of i3status is that you can specify which "modules" should be used (the order directive)\&. You can then configure each module with its own section\&. For every module, you can specify the output format\&. See below for a complete reference\&. 91 | .PP 92 | \fBSample configuration\fR. 93 | .sp 94 | .if n \{\ 95 | .RS 4 96 | .\} 97 | .nf 98 | general { 99 | output_format = "dzen2" 100 | colors = true 101 | interval = 5 102 | } 103 | 104 | order += "ipv6" 105 | order += "disk /" 106 | order += "run_watch DHCP" 107 | order += "run_watch VPNC" 108 | order += "path_exists VPN" 109 | order += "wireless wlan0" 110 | order += "ethernet eth0" 111 | order += "battery 0" 112 | order += "cpu_temperature 0" 113 | order += "load" 114 | order += "tztime local" 115 | order += "tztime berlin" 116 | 117 | wireless wlan0 { 118 | format_up = "W: (%quality at %essid, %bitrate) %ip" 119 | format_down = "W: down" 120 | } 121 | 122 | ethernet eth0 { 123 | # if you use %speed, i3status requires the cap_net_admin capability 124 | format_up = "E: %ip (%speed)" 125 | format_down = "E: down" 126 | } 127 | 128 | battery 0 { 129 | format = "%status %percentage %remaining %emptytime" 130 | format_down = "No battery" 131 | path = "/sys/class/power_supply/BAT%d/uevent" 132 | low_threshold = 10 133 | } 134 | 135 | run_watch DHCP { 136 | pidfile = "/var/run/dhclient*\&.pid" 137 | } 138 | 139 | run_watch VPNC { 140 | # file containing the PID of a vpnc process 141 | pidfile = "/var/run/vpnc/pid" 142 | } 143 | 144 | path_exists VPN { 145 | # path exists when a VPN tunnel launched by nmcli/nm\-applet is active 146 | path = "/proc/sys/net/ipv4/conf/tun0" 147 | } 148 | 149 | tztime local { 150 | format = "%Y\-%m\-%d %H:%M:%S" 151 | } 152 | 153 | tztime berlin { 154 | format = "%Y\-%m\-%d %H:%M:%S %Z" 155 | timezone = "Europe/Berlin" 156 | } 157 | 158 | load { 159 | format = "%5min" 160 | } 161 | 162 | cpu_temperature 0 { 163 | format = "T: %degrees \(deC" 164 | path = "/sys/devices/platform/coretemp\&.0/temp1_input" 165 | } 166 | 167 | disk "/" { 168 | format = "%free" 169 | } 170 | .fi 171 | .if n \{\ 172 | .RE 173 | .\} 174 | .sp 175 | .SS "General" 176 | .sp 177 | The colors directive will disable all colors if you set it to false\&. You can also specify the colors that will be used to display "good", "degraded" or "bad" values using the color_good, color_degraded or color_bad directives, respectively\&. Those directives are only used if color support is not disabled by the colors directive\&. The input format for color values is the canonical RGB hexadecimal triplet (with no separators between the colors), prefixed by a hash character ("#")\&. 178 | .sp 179 | \fBExample configuration\fR: 180 | .sp 181 | .if n \{\ 182 | .RS 4 183 | .\} 184 | .nf 185 | color_good = "#00FF00" 186 | .fi 187 | .if n \{\ 188 | .RE 189 | .\} 190 | .sp 191 | Likewise, you can use the color_separator directive to specify the color that will be used to paint the separator bar\&. The separator is always output in color, even when colors are disabled by the colors directive\&. 192 | .sp 193 | The interval directive specifies the time in seconds for which i3status will sleep before printing the next status line\&. 194 | .sp 195 | Using output_format you can chose which format strings i3status should use in its output\&. Currently available are: 196 | .PP 197 | i3bar 198 | .RS 4 199 | i3bar comes with i3 and provides a workspace bar which does the right thing in multi\-monitor situations\&. It also comes with tray support and can display the i3status output\&. This output type uses JSON to pass as much meta\-information to i3bar as possible (like colors, which blocks can be shortened in which way, etc\&.)\&. 200 | .RE 201 | .PP 202 | dzen2 203 | .RS 4 204 | 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 205 | .RE 206 | .PP 207 | xmobar 208 | .RS 4 209 | xmobar is a minimalistic, text based, status bar\&. It was designed to work with the xmonad Window Manager\&. 210 | .RE 211 | .PP 212 | term 213 | .RS 4 214 | Use ANSI Escape sequences to produce a terminal\-output as close as possible to the graphical outputs\&. This makes debugging your config file a little bit easier because the terminal\-output of i3status becomes much more readable, but should only used for such quick glances, because it will only support very basic output\-features (for example you only get 3 bits of color depth)\&. 215 | .RE 216 | .PP 217 | none 218 | .RS 4 219 | Does not use any color codes\&. Separates values by the pipe symbol\&. This should be used with i3bar and can be used for custom scripts\&. 220 | .RE 221 | .sp 222 | It\(cqs also possible to use the color_good, color_degraded, color_bad directives to define specific colors per module\&. If one of these directives is defined in a module section its value will override the value defined in the general section just for this module\&. 223 | .SS "IPv6" 224 | .sp 225 | This module gets the IPv6 address used for outgoing connections (that is, the best available public IPv6 address on your computer)\&. 226 | .sp 227 | \fBExample format_up\fR: %ip 228 | .sp 229 | \fBExample format_down\fR: no IPv6 230 | .SS "Disk" 231 | .sp 232 | Gets used, free, available and total amount of bytes on the given mounted filesystem\&. 233 | .sp 234 | These values can also be expressed in percentages with the percentage_used, percentage_free, percentage_avail and percentage_used_of_avail formats\&. 235 | .sp 236 | Byte sizes are presented in a human readable format using a set of prefixes whose type can be specified via the "prefix_type" option\&. Three sets of prefixes are available: 237 | .PP 238 | binary 239 | .RS 4 240 | IEC prefixes (Ki, Mi, Gi, Ti) represent multiples of powers of 1024\&. This is the default\&. 241 | .RE 242 | .PP 243 | decimal 244 | .RS 4 245 | SI prefixes (k, M, G, T) represent multiples of powers of 1000\&. 246 | .RE 247 | .PP 248 | custom 249 | .RS 4 250 | The custom prefixes (K, M, G, T) represent multiples of powers of 1024\&. 251 | .RE 252 | .sp 253 | \fBExample order\fR: disk /mnt/usbstick 254 | .sp 255 | \fBExample format\fR: %free (%avail)/ %total 256 | .sp 257 | \fBExample format\fR: %percentage_used used, %percentage_free free, %percentage_avail avail 258 | .sp 259 | \fBExample prefix_type\fR: custom 260 | .SS "Run\-watch" 261 | .sp 262 | Expands the given path to a pidfile and checks if the process ID found inside is valid (that is, if the process is running)\&. You can use this to check if a specific application, such as a VPN client or your DHCP client is running\&. 263 | .sp 264 | \fBExample order\fR: run_watch DHCP 265 | .sp 266 | \fBExample format\fR: %title: %status 267 | .SS "Path\-exists" 268 | .sp 269 | Checks if the given path exists in the filesystem\&. You can use this to check if something is active, like for example a VPN tunnel managed by NetworkManager\&. 270 | .sp 271 | \fBExample order\fR: path_exists VPN 272 | .sp 273 | \fBExample format\fR: %title: %status 274 | .SS "Wireless" 275 | .sp 276 | Gets the link quality and ESSID of the given wireless network interface\&. You can specify different format strings for the network being connected or not connected\&. 277 | .sp 278 | \fBExample order\fR: wireless wlan0 279 | .sp 280 | \fBExample format\fR: W: (%quality at %essid, %bitrate) %ip 281 | .SS "Ethernet" 282 | .sp 283 | Gets the IP address and (if possible) the link speed of the given ethernet interface\&. Getting the link speed requires the cap_net_admin capability\&. Set it using setcap cap_net_admin=ep $(which i3status)\&. 284 | .sp 285 | \fBExample order\fR: ethernet eth0 286 | .sp 287 | \fBExample format\fR: E: %ip (%speed) 288 | .SS "Battery" 289 | .sp 290 | Gets the status (charging, discharging, running), percentage, remaining time and power consumption (in Watts) of the given battery and when it\(cqs 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\(cqs 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\&. 291 | .sp 292 | If you want the battery percentage to be shown without decimals, add integer_battery_capacity = true\&. 293 | .sp 294 | If your battery is represented in a non\-standard path in /sys, be sure to modify the "path" property accordingly, i\&.e\&. pointing to the uevent file on your system\&. The first occurence of %d gets replaced with the battery number, but you can just hard\-code a path as well\&. 295 | .sp 296 | It is possible to define a low_threshold that causes the battery text to be 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\&. 297 | .sp 298 | Will also send libnotify notifications whenever battery status changes (when the computer is plugged in or unplugged, or when the battery becomes full), or whenever your battery crosses the configured low_threshold\&. The format of these notifications can be controlled with notif_header_format and notif_body_format 299 | .sp 300 | \fBExample order\fR: battery 0 301 | .sp 302 | \fBExample format\fR: %status %remaining (%emptytime %consumption) 303 | .sp 304 | \fBExample format_down\fR: No battery 305 | .sp 306 | \fBExample low_threshold\fR: 30 307 | .sp 308 | \fBExample threshold_type\fR: time 309 | .sp 310 | \fBExample path\fR: /sys/class/power_supply/CMB1/uevent 311 | .SS "CPU\-Temperature" 312 | .sp 313 | Gets the temperature of the given thermal zone\&. It is possible to define a max_threshold that will color the temperature red in case the specified thermal zone is getting too hot\&. Defaults to 75 degrees C\&. 314 | .sp 315 | \fBExample order\fR: cpu_temperature 0 316 | .sp 317 | \fBExample format\fR: T: %degrees \(deC 318 | .sp 319 | \fBExample max_threshold\fR: 42 320 | .sp 321 | \fBExample path\fR: /sys/devices/platform/coretemp\&.0/temp1_input 322 | .SS "CPU Usage" 323 | .sp 324 | Gets the percentual CPU usage from /proc/stat (Linux) or sysctl(3) (FreeBSD/OpenBSD)\&. 325 | .sp 326 | \fBExample order\fR: cpu_usage 327 | .sp 328 | \fBExample format\fR: %usage 329 | .SS "Load" 330 | .sp 331 | Gets the system load (number of processes waiting for CPU time in the last 1, 5 and 15 minutes)\&. It is possible to define a max_threshold that will color the load value red in case the load average of the last minute is getting higher than the configured threshold\&. Defaults to 5\&. 332 | .sp 333 | \fBExample order\fR: load 334 | .sp 335 | \fBExample format\fR: %1min %5min %15min 336 | .sp 337 | \fBExample max_threshold\fR: "0,1" 338 | .SS "Time" 339 | .sp 340 | Outputs the current time in the local timezone\&. To use a different timezone, you can set the TZ environment variable, or use the tztime module\&. See strftime(3) for details on the format string\&. 341 | .sp 342 | \fBExample order\fR: time 343 | .sp 344 | \fBExample format\fR: %Y\-%m\-%d %H:%M:%S 345 | .SS "TzTime" 346 | .sp 347 | Outputs the current time in the given timezone\&. If no timezone is given, local time will be used\&. See strftime(3) for details on the format string\&. The system\(cqs timezone database is usually installed in /usr/share/zoneinfo\&. Files below that path make for valid timezone strings, e\&.g\&. for /usr/share/zoneinfo/Europe/Berlin you can set timezone to Europe/Berlin in the tztime module\&. 348 | .sp 349 | \fBExample order\fR: tztime berlin 350 | .sp 351 | \fBExample format\fR: %Y\-%m\-%d %H:%M:%S %Z 352 | .sp 353 | \fBExample timezone\fR: Europe/Berlin 354 | .SS "DDate" 355 | .sp 356 | Outputs the current discordian date in user\-specified format\&. See ddate(1) for details on the format string\&. \fBNote\fR: Neither \fB%\&.\fR nor \fB%X\fR are implemented yet\&. 357 | .sp 358 | \fBExample order\fR: ddate 359 | .sp 360 | \fBExample format\fR: %{%a, %b %d%}, %Y%N \- %H 361 | .SS "Volume" 362 | .sp 363 | Outputs the volume of the specified mixer on the specified device\&. Works only on Linux because it uses ALSA\&. A simplified configuration can be used on FreeBSD and OpenBSD due to the lack of ALSA, the device and mixer options can be ignored on these systems\&. On these systems the OSS API is used instead to query /dev/mixer directly if mixer_dix is \-1, otherwise /dev/mixer+mixer_idx+\&. 364 | .sp 365 | \fBExample order\fR: volume master 366 | .sp 367 | \fBExample format\fR: ♪: %volume \fBExample format_muted\fR: ♪: 0%% 368 | .sp 369 | \fBExample configuration\fR: 370 | .sp 371 | .if n \{\ 372 | .RS 4 373 | .\} 374 | .nf 375 | volume master { 376 | format = "♪: %volume" 377 | format_muted = "♪: muted (%volume)" 378 | device = "default" 379 | mixer = "Master" 380 | mixer_idx = 0 381 | } 382 | .fi 383 | .if n \{\ 384 | .RE 385 | .\} 386 | .SS "MPD" 387 | .sp 388 | Outputs the currently playing MPD track, using libmpdclient\&. Will display a libnotify notification whenever the track changes\&. 389 | .sp 390 | \fBExample order\fR: mpd 391 | .sp 392 | \fBExample format\fR: %artist \- %album \- %title 393 | .sp 394 | \fBExample format_stopped\fR: Stopped 395 | .sp 396 | \fBExample notif_header_format\fR: %title 397 | .sp 398 | \fBExample notif_body_format\fR: %artist \- %album 399 | .SH "USING I3STATUS WITH DZEN2" 400 | .sp 401 | After installing dzen2, you can directly use it with i3status\&. Just ensure that output_format is set to dzen2\&. 402 | .sp 403 | \fBExample for usage of i3status with dzen2\fR: 404 | .sp 405 | .if n \{\ 406 | .RS 4 407 | .\} 408 | .nf 409 | i3status | dzen2 \-fg white \-ta r \-w 1280 \e 410 | \-fn "\-misc\-fixed\-medium\-r\-normal\-\-13\-120\-75\-75\-C\-70\-iso8859\-1" 411 | .fi 412 | .if n \{\ 413 | .RE 414 | .\} 415 | .SH "USING I3STATUS WITH XMOBAR" 416 | .sp 417 | 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\&. 418 | .sp 419 | \fBExample for usage of i3status with xmobar\fR: 420 | .sp 421 | .if n \{\ 422 | .RS 4 423 | .\} 424 | .nf 425 | i3status | xmobar \-o \-t "%StdinReader%" \-c "[Run StdinReader]" 426 | .fi 427 | .if n \{\ 428 | .RE 429 | .\} 430 | .SH "WHAT ABOUT MEMORY USAGE OR CPU FREQUENCY?" 431 | .sp 432 | While talking about two specific things, please understand this section as a general explanation why your favorite information is not included in i3status\&. 433 | .sp 434 | Let\(cqs 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\(cqs 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\(cqt 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\&. 435 | .sp 436 | For CPU frequency, the situation is similar\&. Many people don\(cqt 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\(cqt tell you anything useful either\&. 437 | .sp 438 | In general, i3status wants to display things which you would look at occasionally anyways, like the current date/time, whether you are connected to a WiFi network or not, and if you have enough disk space to fit that 4\&.3 GiB download\&. 439 | .sp 440 | 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\&. 441 | .SH "EXTERNAL SCRIPTS/PROGRAMS WITH I3STATUS" 442 | .sp 443 | In i3status, we don\(cqt want to implement process management again\&. Therefore, there is no module to run arbitrary scripts or commands\&. Instead, you should use your shell, for example like this: 444 | .sp 445 | \fBExample for prepending the i3status output\fR: 446 | .sp 447 | .if n \{\ 448 | .RS 4 449 | .\} 450 | .nf 451 | #!/bin/sh 452 | # shell script to prepend i3status with more stuff 453 | 454 | i3status | while : 455 | do 456 | read line 457 | echo "mystuff | $line" || exit 1 458 | done 459 | .fi 460 | .if n \{\ 461 | .RE 462 | .\} 463 | .sp 464 | Put that in some script, say \&.bin/my_i3status\&.sh and execute that instead of i3status\&. 465 | .sp 466 | Note that if you want to use the JSON output format (with colors in i3bar), you need to use a slightly more complex wrapper script\&. There are examples in the contrib/ folder, see http://code\&.i3wm\&.org/i3status/tree/contrib 467 | .SH "SIGNALS" 468 | .sp 469 | When receiving SIGUSR1, i3status\(cqs nanosleep() will be interrupted and thus you will force an update\&. You can use killall \-USR1 i3status to force an update after changing the system volume, for example\&. 470 | .SH "SEE ALSO" 471 | .sp 472 | strftime(3), date(1), glob(3), dzen2(1), xmobar(1) 473 | .SH "AUTHORS" 474 | .sp 475 | Michael Stapelberg and contributors 476 | .sp 477 | Thorsten Toepper 478 | .sp 479 | Baptiste Daroussin 480 | .sp 481 | Axel Wagner 482 | .sp 483 | Fernando Tarlá Cardoso Lemos 484 | -------------------------------------------------------------------------------- /i3status.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=8:expandtab 3 | * 4 | * i3status – Generates a status line for dzen2 or xmobar 5 | * 6 | * Copyright © 2014 Griffin Smith 7 | * Copyright © 2008-2012 Michael Stapelberg and contributors 8 | * Copyright © 2009 Thorsten Toepper 9 | * Copyright © 2010 Axel Wagner 10 | * Copyright © 2010 Fernando Tarlá Cardoso Lemos 11 | * 12 | * See file LICENSE for license information. 13 | * 14 | */ 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "i3status.h" 37 | 38 | #define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); } 39 | 40 | #define CFG_COLOR_OPTS(good, degraded, bad) \ 41 | CFG_STR("color_good", good, CFGF_NONE), \ 42 | CFG_STR("color_degraded", degraded, CFGF_NONE), \ 43 | CFG_STR("color_bad", bad, CFGF_NONE) 44 | 45 | #define CFG_CUSTOM_COLOR_OPTS CFG_COLOR_OPTS(NULL, NULL, NULL) 46 | 47 | /* socket file descriptor for general purposes */ 48 | int general_socket; 49 | 50 | static bool exit_upon_signal = false; 51 | 52 | cfg_t *cfg, *cfg_general, *cfg_section; 53 | 54 | /* 55 | * Set the exit_upon_signal flag, because one cannot do anything in a safe 56 | * manner in a signal handler (e.g. fprintf, which we really want to do for 57 | * debugging purposes), see 58 | * https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers 59 | * 60 | */ 61 | void fatalsig(int signum) { 62 | exit_upon_signal = true; 63 | } 64 | 65 | /* 66 | * Do nothing upon SIGUSR1. Running this signal handler will nevertheless 67 | * interrupt nanosleep() so that i3status immediately generates new output. 68 | * 69 | */ 70 | void sigusr1(int signum) { 71 | } 72 | 73 | /* 74 | * Checks if the given path exists by calling stat(). 75 | * 76 | */ 77 | static bool path_exists(const char *path) { 78 | struct stat buf; 79 | return (stat(path, &buf) == 0); 80 | } 81 | 82 | static void *scalloc(size_t size) { 83 | void *result = calloc(size, 1); 84 | exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); 85 | return result; 86 | } 87 | 88 | static char *sstrdup(const char *str) { 89 | char *result = strdup(str); 90 | exit_if_null(result, "Error: out of memory (strdup())\n"); 91 | return result; 92 | } 93 | 94 | 95 | /* 96 | * Validates a color in "#RRGGBB" format 97 | * 98 | */ 99 | static int valid_color(const char *value) 100 | { 101 | if (strlen(value) != 7) return 0; 102 | if (value[0] != '#') return 0; 103 | for (int i = 1; i < 7; ++i) { 104 | if (value[i] >= '0' && value[i] <= '9') continue; 105 | if (value[i] >= 'a' && value[i] <= 'f') continue; 106 | if (value[i] >= 'A' && value[i] <= 'F') continue; 107 | return 0; 108 | } 109 | return 1; 110 | } 111 | 112 | /* 113 | * This function resolves ~ in pathnames. 114 | * It may resolve wildcards in the first part of the path, but if no match 115 | * or multiple matches are found, it just returns a copy of path as given. 116 | * 117 | */ 118 | static char *resolve_tilde(const char *path) { 119 | static glob_t globbuf; 120 | char *head, *tail, *result = NULL; 121 | 122 | tail = strchr(path, '/'); 123 | head = strndup(path, tail ? (size_t)(tail - path) : strlen(path)); 124 | 125 | int res = glob(head, GLOB_TILDE, NULL, &globbuf); 126 | free(head); 127 | /* no match, or many wildcard matches are bad */ 128 | if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) 129 | result = sstrdup(path); 130 | else if (res != 0) { 131 | die("glob() failed"); 132 | } else { 133 | head = globbuf.gl_pathv[0]; 134 | result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); 135 | strncpy(result, head, strlen(head)); 136 | if (tail) 137 | strncat(result, tail, strlen(tail)); 138 | } 139 | globfree(&globbuf); 140 | 141 | return result; 142 | } 143 | 144 | static char *get_config_path(void) { 145 | char *xdg_config_home, *xdg_config_dirs, *config_path; 146 | 147 | /* 1: check the traditional path under the home directory */ 148 | config_path = resolve_tilde("~/.i3status.conf"); 149 | if (path_exists(config_path)) 150 | return config_path; 151 | 152 | /* 2: check for $XDG_CONFIG_HOME/i3status/config */ 153 | if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) 154 | xdg_config_home = "~/.config"; 155 | 156 | xdg_config_home = resolve_tilde(xdg_config_home); 157 | if (asprintf(&config_path, "%s/i3status/config", xdg_config_home) == -1) 158 | die("asprintf() failed"); 159 | free(xdg_config_home); 160 | 161 | if (path_exists(config_path)) 162 | return config_path; 163 | free(config_path); 164 | 165 | /* 3: check the traditional path under /etc */ 166 | config_path = SYSCONFDIR "/i3status.conf"; 167 | if (path_exists(config_path)) 168 | return sstrdup(config_path); 169 | 170 | /* 4: check for $XDG_CONFIG_DIRS/i3status/config */ 171 | if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) 172 | xdg_config_dirs = "/etc/xdg"; 173 | 174 | char *buf = strdup(xdg_config_dirs); 175 | char *tok = strtok(buf, ":"); 176 | while (tok != NULL) { 177 | tok = resolve_tilde(tok); 178 | if (asprintf(&config_path, "%s/i3status/config", tok) == -1) 179 | die("asprintf() failed"); 180 | free(tok); 181 | if (path_exists(config_path)) { 182 | free(buf); 183 | return config_path; 184 | } 185 | free(config_path); 186 | tok = strtok(NULL, ":"); 187 | } 188 | free(buf); 189 | 190 | die("Unable to find the configuration file (looked at " 191 | "~/.i3status.conf, $XDG_CONFIG_HOME/i3status/config, " 192 | "/etc/i3status.conf and $XDG_CONFIG_DIRS/i3status/config)"); 193 | return NULL; 194 | } 195 | 196 | int main(int argc, char *argv[]) { 197 | unsigned int j; 198 | 199 | cfg_opt_t general_opts[] = { 200 | CFG_STR("output_format", "auto", CFGF_NONE), 201 | CFG_BOOL("colors", 1, CFGF_NONE), 202 | CFG_STR("color_separator", "#333333", CFGF_NONE), 203 | CFG_INT("interval", 1, CFGF_NONE), 204 | CFG_COLOR_OPTS("#00FF00", "#FFFF00", "#FF0000"), 205 | CFG_END() 206 | }; 207 | 208 | cfg_opt_t mpd_opts[] = { 209 | CFG_STR("format", "auto", CFGF_NONE), 210 | CFG_STR("format_stopped", "Stopped", CFGF_NONE), 211 | CFG_STR("notif_header_format", "%title", CFGF_NONE), 212 | CFG_STR("notif_body_format", "%artist - %album", CFGF_NONE), 213 | CFG_END() 214 | }; 215 | 216 | cfg_opt_t run_watch_opts[] = { 217 | CFG_STR("pidfile", NULL, CFGF_NONE), 218 | CFG_STR("format", "%title: %status", CFGF_NONE), 219 | CFG_CUSTOM_COLOR_OPTS, 220 | CFG_END() 221 | }; 222 | 223 | cfg_opt_t path_exists_opts[] = { 224 | CFG_STR("path", NULL, CFGF_NONE), 225 | CFG_STR("format", "%title: %status", CFGF_NONE), 226 | CFG_CUSTOM_COLOR_OPTS, 227 | CFG_END() 228 | }; 229 | 230 | cfg_opt_t wireless_opts[] = { 231 | CFG_STR("format_up", "W: (%quality at %essid, %bitrate) %ip", CFGF_NONE), 232 | CFG_STR("format_down", "W: down", CFGF_NONE), 233 | CFG_CUSTOM_COLOR_OPTS, 234 | CFG_END() 235 | }; 236 | 237 | cfg_opt_t ethernet_opts[] = { 238 | CFG_STR("format_up", "E: %ip (%speed)", CFGF_NONE), 239 | CFG_STR("format_down", "E: down", CFGF_NONE), 240 | CFG_CUSTOM_COLOR_OPTS, 241 | CFG_END() 242 | }; 243 | 244 | cfg_opt_t ipv6_opts[] = { 245 | CFG_STR("format_up", "%ip", CFGF_NONE), 246 | CFG_STR("format_down", "no IPv6", CFGF_NONE), 247 | CFG_CUSTOM_COLOR_OPTS, 248 | CFG_END() 249 | }; 250 | 251 | cfg_opt_t battery_opts[] = { 252 | CFG_STR("format", "%status %percentage %remaining", CFGF_NONE), 253 | CFG_STR("format_down", "No battery", CFGF_NONE), 254 | CFG_STR("notif_header_format", "Battery: %status", CFGF_NONE), 255 | CFG_STR("notif_body_format", "%percentage, %remaining remaining", CFGF_NONE), 256 | CFG_STR("path", "/sys/class/power_supply/BAT%d/uevent", CFGF_NONE), 257 | CFG_INT("low_threshold", 30, CFGF_NONE), 258 | CFG_INT("critical_threshold", 10, CFGF_NONE), 259 | CFG_STR("threshold_type", "time", CFGF_NONE), 260 | CFG_BOOL("last_full_capacity", false, CFGF_NONE), 261 | CFG_BOOL("integer_battery_capacity", false, CFGF_NONE), 262 | CFG_CUSTOM_COLOR_OPTS, 263 | CFG_END() 264 | }; 265 | 266 | cfg_opt_t time_opts[] = { 267 | CFG_STR("format", "%Y-%m-%d %H:%M:%S", CFGF_NONE), 268 | CFG_END() 269 | }; 270 | 271 | cfg_opt_t tztime_opts[] = { 272 | CFG_STR("format", "%Y-%m-%d %H:%M:%S %Z", CFGF_NONE), 273 | CFG_STR("timezone", "", CFGF_NONE), 274 | CFG_END() 275 | }; 276 | 277 | cfg_opt_t ddate_opts[] = { 278 | CFG_STR("format", "%{%a, %b %d%}, %Y%N - %H", CFGF_NONE), 279 | CFG_END() 280 | }; 281 | 282 | cfg_opt_t load_opts[] = { 283 | CFG_STR("format", "%1min %5min %15min", CFGF_NONE), 284 | CFG_FLOAT("max_threshold", 5, CFGF_NONE), 285 | CFG_CUSTOM_COLOR_OPTS, 286 | CFG_END() 287 | }; 288 | 289 | cfg_opt_t usage_opts[] = { 290 | CFG_STR("format", "%usage", CFGF_NONE), 291 | CFG_END() 292 | }; 293 | 294 | cfg_opt_t temp_opts[] = { 295 | CFG_STR("format", "%degrees C", CFGF_NONE), 296 | CFG_STR("path", NULL, CFGF_NONE), 297 | CFG_INT("max_threshold", 75, CFGF_NONE), 298 | CFG_CUSTOM_COLOR_OPTS, 299 | CFG_END() 300 | }; 301 | 302 | cfg_opt_t disk_opts[] = { 303 | CFG_STR("format", "%free", CFGF_NONE), 304 | CFG_STR("prefix_type", "binary", CFGF_NONE), 305 | CFG_END() 306 | }; 307 | 308 | cfg_opt_t volume_opts[] = { 309 | CFG_STR("format", "♪: %volume", CFGF_NONE), 310 | CFG_STR("format_muted", "♪: 0%%", CFGF_NONE), 311 | CFG_STR("device", "default", CFGF_NONE), 312 | CFG_STR("mixer", "Master", CFGF_NONE), 313 | CFG_INT("mixer_idx", 0, CFGF_NONE), 314 | CFG_CUSTOM_COLOR_OPTS, 315 | CFG_END() 316 | }; 317 | 318 | cfg_opt_t opts[] = { 319 | CFG_STR_LIST("order", "{}", CFGF_NONE), 320 | CFG_SEC("general", general_opts, CFGF_NONE), 321 | CFG_SEC("mpd", mpd_opts, CFGF_NONE), 322 | CFG_SEC("run_watch", run_watch_opts, CFGF_TITLE | CFGF_MULTI), 323 | CFG_SEC("path_exists", path_exists_opts, CFGF_TITLE | CFGF_MULTI), 324 | CFG_SEC("wireless", wireless_opts, CFGF_TITLE | CFGF_MULTI), 325 | CFG_SEC("ethernet", ethernet_opts, CFGF_TITLE | CFGF_MULTI), 326 | CFG_SEC("battery", battery_opts, CFGF_TITLE | CFGF_MULTI), 327 | CFG_SEC("cpu_temperature", temp_opts, CFGF_TITLE | CFGF_MULTI), 328 | CFG_SEC("disk", disk_opts, CFGF_TITLE | CFGF_MULTI), 329 | CFG_SEC("volume", volume_opts, CFGF_TITLE | CFGF_MULTI), 330 | CFG_SEC("ipv6", ipv6_opts, CFGF_NONE), 331 | CFG_SEC("time", time_opts, CFGF_NONE), 332 | CFG_SEC("tztime", tztime_opts, CFGF_TITLE | CFGF_MULTI), 333 | CFG_SEC("ddate", ddate_opts, CFGF_NONE), 334 | CFG_SEC("load", load_opts, CFGF_NONE), 335 | CFG_SEC("cpu_usage", usage_opts, CFGF_NONE), 336 | CFG_CUSTOM_COLOR_OPTS, 337 | CFG_END() 338 | }; 339 | 340 | char *configfile = NULL; 341 | int o, option_index = 0; 342 | struct option long_options[] = { 343 | {"config", required_argument, 0, 'c'}, 344 | {"help", no_argument, 0, 'h'}, 345 | {"version", no_argument, 0, 'v'}, 346 | {0, 0, 0, 0} 347 | }; 348 | 349 | struct sigaction action; 350 | memset(&action, 0, sizeof(struct sigaction)); 351 | action.sa_handler = fatalsig; 352 | 353 | /* Exit upon SIGPIPE because when we have nowhere to write to, gathering system 354 | * information is pointless. Also exit explicitly on SIGTERM and SIGINT because 355 | * only this will trigger a reset of the cursor in the terminal output-format. 356 | */ 357 | sigaction(SIGPIPE, &action, NULL); 358 | sigaction(SIGTERM, &action, NULL); 359 | sigaction(SIGINT, &action, NULL); 360 | 361 | memset(&action, 0, sizeof(struct sigaction)); 362 | action.sa_handler = sigusr1; 363 | sigaction(SIGUSR1, &action, NULL); 364 | 365 | if (setlocale(LC_ALL, "") == NULL) 366 | die("Could not set locale. Please make sure all your LC_* / LANG settings are correct."); 367 | 368 | while ((o = getopt_long(argc, argv, "c:hv", long_options, &option_index)) != -1) 369 | if ((char)o == 'c') 370 | configfile = optarg; 371 | else if ((char)o == 'h') { 372 | printf("i3status " VERSION " © 2008-2012 Michael Stapelberg and contributors\n" 373 | "Syntax: %s [-c ] [-h] [-v]\n", argv[0]); 374 | return 0; 375 | } else if ((char)o == 'v') { 376 | printf("i3status " VERSION " © 2008-2012 Michael Stapelberg and contributors\n"); 377 | return 0; 378 | } 379 | 380 | 381 | if (configfile == NULL) 382 | configfile = get_config_path(); 383 | 384 | cfg = cfg_init(opts, CFGF_NOCASE); 385 | if (cfg_parse(cfg, configfile) == CFG_PARSE_ERROR) 386 | return EXIT_FAILURE; 387 | 388 | if (cfg_size(cfg, "order") == 0) 389 | die("Your 'order' array is empty. Please fix your config.\n"); 390 | 391 | cfg_general = cfg_getsec(cfg, "general"); 392 | if (cfg_general == NULL) 393 | die("Could not get section \"general\"\n"); 394 | 395 | char *output_str = cfg_getstr(cfg_general, "output_format"); 396 | if (strcasecmp(output_str, "auto") == 0) { 397 | fprintf(stderr, "i3status: trying to auto-detect output_format setting\n"); 398 | output_str = auto_detect_format(); 399 | if (!output_str) { 400 | output_str = "none"; 401 | fprintf(stderr, "i3status: falling back to \"none\"\n"); 402 | } else { 403 | fprintf(stderr, "i3status: auto-detected \"%s\"\n", output_str); 404 | } 405 | } 406 | 407 | if (strcasecmp(output_str, "dzen2") == 0) 408 | output_format = O_DZEN2; 409 | else if (strcasecmp(output_str, "xmobar") == 0) 410 | output_format = O_XMOBAR; 411 | else if (strcasecmp(output_str, "i3bar") == 0) 412 | output_format = O_I3BAR; 413 | else if (strcasecmp(output_str, "term") == 0) 414 | output_format = O_TERM; 415 | else if (strcasecmp(output_str, "none") == 0) 416 | output_format = O_NONE; 417 | else die("Unknown output format: \"%s\"\n", output_str); 418 | 419 | if (!valid_color(cfg_getstr(cfg_general, "color_good")) 420 | || !valid_color(cfg_getstr(cfg_general, "color_degraded")) 421 | || !valid_color(cfg_getstr(cfg_general, "color_bad")) 422 | || !valid_color(cfg_getstr(cfg_general, "color_separator"))) 423 | die("Bad color format"); 424 | 425 | #if YAJL_MAJOR >= 2 426 | yajl_gen json_gen = yajl_gen_alloc(NULL); 427 | #else 428 | yajl_gen json_gen = yajl_gen_alloc(NULL, NULL); 429 | #endif 430 | 431 | if (output_format == O_I3BAR) { 432 | /* Initialize the i3bar protocol. See i3/docs/i3bar-protocol 433 | * for details. */ 434 | printf("{\"version\":1}\n[\n"); 435 | fflush(stdout); 436 | yajl_gen_array_open(json_gen); 437 | yajl_gen_clear(json_gen); 438 | } 439 | if (output_format == O_TERM) { 440 | /* Save the cursor-position and hide the cursor */ 441 | printf("\033[s\033[?25l"); 442 | /* Undo at exit */ 443 | atexit(&reset_cursor); 444 | } 445 | 446 | if ((general_socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 447 | die("Could not create socket\n"); 448 | 449 | // Initialize libnotify 450 | notify_init("i3status"); 451 | 452 | int interval = cfg_getint(cfg_general, "interval"); 453 | 454 | /* One memory page which each plugin can use to buffer output. 455 | * Even though it’s unclean, we just assume that the user will not 456 | * specify a format string which expands to something longer than 4096 457 | * bytes — given that the output of i3status is used to display 458 | * information on screen, more than 1024 characters for the full line 459 | * (!), not individual plugins, seem very unlikely. */ 460 | char buffer[4096]; 461 | 462 | while (1) { 463 | if (exit_upon_signal) { 464 | fprintf(stderr, "Exiting due to signal.\n"); 465 | cleanup_mpd(); 466 | notify_uninit(); 467 | exit(1); 468 | } 469 | struct timeval tv; 470 | gettimeofday(&tv, NULL); 471 | if (output_format == O_I3BAR) 472 | yajl_gen_array_open(json_gen); 473 | else if (output_format == O_TERM) 474 | /* Restore the cursor-position, clear line */ 475 | printf("\033[u\033[K"); 476 | for (j = 0; j < cfg_size(cfg, "order"); j++) { 477 | if (j > 0) 478 | print_seperator(); 479 | 480 | const char *current = cfg_getnstr(cfg, "order", j); 481 | 482 | CASE_SEC("mpd") { 483 | SEC_OPEN_MAP("mpd"); 484 | print_mpd(json_gen, buffer, 485 | cfg_getstr(sec, "format"), 486 | cfg_getstr(sec, "format_stopped"), 487 | cfg_getstr(sec, "notif_header_format"), 488 | cfg_getstr(sec, "notif_body_format")); 489 | SEC_CLOSE_MAP; 490 | } 491 | 492 | CASE_SEC("ipv6") { 493 | SEC_OPEN_MAP("ipv6"); 494 | print_ipv6_info(json_gen, buffer, cfg_getstr(sec, "format_up"), cfg_getstr(sec, "format_down")); 495 | SEC_CLOSE_MAP; 496 | } 497 | 498 | CASE_SEC_TITLE("wireless") { 499 | SEC_OPEN_MAP("wireless"); 500 | print_wireless_info(json_gen, buffer, title, cfg_getstr(sec, "format_up"), cfg_getstr(sec, "format_down")); 501 | SEC_CLOSE_MAP; 502 | } 503 | 504 | CASE_SEC_TITLE("ethernet") { 505 | SEC_OPEN_MAP("ethernet"); 506 | print_eth_info(json_gen, buffer, title, cfg_getstr(sec, "format_up"), cfg_getstr(sec, "format_down")); 507 | SEC_CLOSE_MAP; 508 | } 509 | 510 | CASE_SEC_TITLE("battery") { 511 | SEC_OPEN_MAP("battery"); 512 | print_battery_info(json_gen, buffer, atoi(title), 513 | cfg_getstr(sec, "path"), 514 | cfg_getstr(sec, "format"), 515 | cfg_getstr(sec, "format_down"), 516 | cfg_getstr(sec, "notif_header_format"), 517 | cfg_getstr(sec, "notif_body_format"), 518 | cfg_getint(sec, "low_threshold"), 519 | cfg_getstr(sec, "threshold_type"), 520 | cfg_getbool(sec, "last_full_capacity"), 521 | cfg_getbool(sec, "integer_battery_capacity")); 522 | SEC_CLOSE_MAP; 523 | } 524 | 525 | CASE_SEC_TITLE("run_watch") { 526 | SEC_OPEN_MAP("run_watch"); 527 | print_run_watch(json_gen, buffer, title, cfg_getstr(sec, "pidfile"), cfg_getstr(sec, "format")); 528 | SEC_CLOSE_MAP; 529 | } 530 | 531 | CASE_SEC_TITLE("path_exists") { 532 | SEC_OPEN_MAP("path_exists"); 533 | print_path_exists(json_gen, buffer, title, cfg_getstr(sec, "path"), cfg_getstr(sec, "format")); 534 | SEC_CLOSE_MAP; 535 | } 536 | 537 | CASE_SEC_TITLE("disk") { 538 | SEC_OPEN_MAP("disk_info"); 539 | print_disk_info(json_gen, buffer, title, cfg_getstr(sec, "format"), cfg_getstr(sec, "prefix_type")); 540 | SEC_CLOSE_MAP; 541 | } 542 | 543 | CASE_SEC("load") { 544 | SEC_OPEN_MAP("load"); 545 | print_load(json_gen, buffer, cfg_getstr(sec, "format"), cfg_getfloat(sec, "max_threshold")); 546 | SEC_CLOSE_MAP; 547 | } 548 | 549 | CASE_SEC("time") { 550 | SEC_OPEN_MAP("time"); 551 | print_time(json_gen, buffer, cfg_getstr(sec, "format"), NULL, tv.tv_sec); 552 | SEC_CLOSE_MAP; 553 | } 554 | 555 | CASE_SEC_TITLE("tztime") { 556 | SEC_OPEN_MAP("tztime"); 557 | print_time(json_gen, buffer, cfg_getstr(sec, "format"), cfg_getstr(sec, "timezone"), tv.tv_sec); 558 | SEC_CLOSE_MAP; 559 | } 560 | 561 | CASE_SEC("ddate") { 562 | SEC_OPEN_MAP("ddate"); 563 | print_ddate(json_gen, buffer, cfg_getstr(sec, "format"), tv.tv_sec); 564 | SEC_CLOSE_MAP; 565 | } 566 | 567 | CASE_SEC_TITLE("volume") { 568 | SEC_OPEN_MAP("volume"); 569 | print_volume(json_gen, buffer, cfg_getstr(sec, "format"), 570 | cfg_getstr(sec, "format_muted"), 571 | cfg_getstr(sec, "device"), 572 | cfg_getstr(sec, "mixer"), 573 | cfg_getint(sec, "mixer_idx")); 574 | SEC_CLOSE_MAP; 575 | } 576 | 577 | CASE_SEC_TITLE("cpu_temperature") { 578 | SEC_OPEN_MAP("cpu_temperature"); 579 | print_cpu_temperature_info(json_gen, buffer, atoi(title), cfg_getstr(sec, "path"), cfg_getstr(sec, "format"), cfg_getint(sec, "max_threshold")); 580 | SEC_CLOSE_MAP; 581 | } 582 | 583 | CASE_SEC("cpu_usage") { 584 | SEC_OPEN_MAP("cpu_usage"); 585 | print_cpu_usage(json_gen, buffer, cfg_getstr(sec, "format")); 586 | SEC_CLOSE_MAP; 587 | } 588 | } 589 | if (output_format == O_I3BAR) { 590 | yajl_gen_array_close(json_gen); 591 | const unsigned char *buf; 592 | #if YAJL_MAJOR >= 2 593 | size_t len; 594 | #else 595 | unsigned int len; 596 | #endif 597 | yajl_gen_get_buf(json_gen, &buf, &len); 598 | write(STDOUT_FILENO, buf, len); 599 | yajl_gen_clear(json_gen); 600 | } 601 | 602 | printf("\n"); 603 | fflush(stdout); 604 | 605 | /* To provide updates on every full second (as good as possible) 606 | * we don’t use sleep(interval) but we sleep until the next 607 | * second (with microsecond precision) plus (interval-1) 608 | * seconds. We also align to 60 seconds modulo interval such 609 | * that we start with :00 on every new minute. */ 610 | struct timeval current_timeval; 611 | gettimeofday(¤t_timeval, NULL); 612 | struct timespec ts = {interval - 1 - (current_timeval.tv_sec % interval), (10e5 - current_timeval.tv_usec) * 1000}; 613 | nanosleep(&ts, NULL); 614 | } 615 | 616 | cleanup_mpd(); 617 | notify_uninit(); 618 | } 619 | --------------------------------------------------------------------------------