├── .gitignore ├── README.md ├── LICENSE ├── .astylerc ├── Makefile └── statusbar.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | statusbar 3 | build_host.h 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Super simple status bar for DWM (for linux). 2 | 3 | Gets autoconfigured during make. 4 | 5 | The default compiler is clang. The program can be built with any sane C compiler (gcc, tcc, etc). Eg.: 6 | 7 | ```bash 8 | make CC=gcc 9 | ``` 10 | **PLEASE NOTE: The makefile will try to infer the wifi interface name, but may require manual change in case the net.ifnames is enabled (predictable interface names)** 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2012-2019 Aleksei Kozadaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.astylerc: -------------------------------------------------------------------------------- 1 | # use kr kernel style 2 | style=java 3 | 4 | # indenet with spaces 5 | indent=spaces=4 6 | 7 | # max line length 8 | max-code-length=98 9 | 10 | # Set the maximum of # spaces to indent a continuation line 11 | max-instatement-indent=98 12 | 13 | # indent classes, so that public/private labels are indented 14 | # indent-classes 15 | 16 | # Attach braces to class and struct inline function definitions. 17 | attach-inlines 18 | 19 | # put closing while on the same line as the block bracket. 20 | attach-closing-while 21 | 22 | # Indent 'switch' blocks so that the 'case X:' statements are indented in the 23 | # switch block. 24 | indent-switches 25 | 26 | # Indent 'case X:' blocks from the 'case X:' headers 27 | # indent-cases 28 | 29 | # leave condition on the previous line 30 | break-after-logical 31 | 32 | # pad operators a=b -> a = b 33 | pad-oper 34 | 35 | # pad statements like 'if', 'for', 'while', so if(a) -> if (a) 36 | pad-header 37 | 38 | # add space after a comma 39 | pad-comma 40 | unpad-paren 41 | 42 | # break one line if/while statements. 43 | break-one-line-headers 44 | 45 | # remove unnecessary padding around parentesis 46 | unpad-paren 47 | 48 | # pointer is aligned to the var name. Eg. char *ptr 49 | align-pointer=name 50 | 51 | # reference is aligned to the var name. Eg. vector &ptr 52 | align-reference=name 53 | 54 | # indent multiline preprocessor defines 55 | indent-preproc-define 56 | indent-preproc-block 57 | 58 | # insert empty lines around blocks 59 | # break-blocks 60 | 61 | # Delete empty lines within a function or method. 62 | # delete-empty-lines 63 | 64 | # add brackets in 1 line 'if', 'for' and 'while' statements 65 | add-brackets 66 | add-braces 67 | 68 | # add brackets even to one-line statements 69 | add-one-line-brackets 70 | 71 | # keep oneline statements unchanged 72 | keep-one-line-statements 73 | 74 | # else is on a separate line 75 | # break-closing-brackets 76 | # break-elseifs 77 | 78 | # Break the return type from the function name. 79 | # break-return-type 80 | attach-return-type 81 | 82 | # bracket on the same line as namespaces 83 | attach-namespaces 84 | 85 | # indent one-line comments 86 | indent-col1-comments 87 | 88 | # add 2 indents when breaking a long line (VER >= 2.6) 89 | # indent-continuation=2 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := statusbar 2 | SRC := $(wildcard *.c) 3 | OBJ := $(SRC:.c=.o) 4 | BUILD_HOST := build_host.h 5 | CC ?= gcc 6 | 7 | INSTALL := install 8 | INSTALL_ARGS := -o root -g root -m 755 9 | INSTALL_DIR := /usr/local/bin/ 10 | 11 | # autoconfiguration 12 | BATPATH := $(strip $(shell find /sys -name BAT? -print0 -quit)) 13 | # Infer the wifi interface name - please override here if necessary 14 | IFNAME := $(shell iw dev | awk '/Interface/ { print $$2 }' | tr -d '\n') 15 | LNKPATH := $(shell find /sys/class/net/$(IFNAME)/ -name operstate -print0 -quit) 16 | 17 | # version info from git 18 | REVCNT := $(shell git rev-list --count main 2>/dev/null) 19 | ifeq ($(REVCNT),) 20 | VERSION := devel 21 | else 22 | REVHASH := $(shell git log -1 --format="%h" 2>/dev/null) 23 | VERSION := "$(REVCNT).$(REVHASH)" 24 | endif 25 | 26 | CFLAGS := -Wall $(shell pkg-config --cflags alsa) 27 | LFLAGS := $(shell pkg-config --libs alsa) 28 | LFLAGS += $(shell pkg-config --libs x11) 29 | 30 | ifeq ($(CC), $(filter $(CC), cc gcc clang)) 31 | CFLAGS += -std=c99 -pedantic 32 | endif 33 | 34 | all: debug 35 | 36 | debug: CFLAGS += -g -ggdb -DDEBUG 37 | debug: LFLAGS += -g 38 | debug: build 39 | 40 | release: CFLAGS += -O3 -I/usr/X11R6/include 41 | release: clean build 42 | 43 | build: $(BUILD_HOST) $(TARGET) 44 | 45 | $(BUILD_HOST): 46 | @echo "#define BUILD_HOST \"`hostname`\"" > $(BUILD_HOST) 47 | @echo "#define BUILD_OS \"`uname`\"" >> $(BUILD_HOST) 48 | @echo "#define BUILD_PLATFORM \"`uname -m`\"" >> $(BUILD_HOST) 49 | @echo "#define BUILD_KERNEL \"`uname -r`\"" >> $(BUILD_HOST) 50 | @echo "#define BUILD_VERSION \"$(VERSION)\"" >> $(BUILD_HOST) 51 | @echo "#define LNK_PATH \"$(LNKPATH)\"" >> $(BUILD_HOST) 52 | ifdef BATPATH 53 | @echo "#define BAT_NOW \"$(BATPATH)/charge_now\"" >> $(BUILD_HOST) 54 | @echo "#define BAT_FULL \"$(BATPATH)/charge_full\"" >> $(BUILD_HOST) 55 | @echo "#define BAT_STAT \"$(BATPATH)/status\"" >> $(BUILD_HOST) 56 | endif 57 | 58 | $(TARGET): $(BUILD_HOST) $(OBJ) 59 | $(CC) -o $@ $(OBJ) $(LFLAGS) 60 | 61 | %.o: %.c 62 | $(CC) $(CFLAGS) -c $< 63 | 64 | install: release 65 | $(INSTALL) $(INSTALL_ARGS) $(TARGET) $(INSTALL_DIR) 66 | @echo "DONE" 67 | 68 | clean: 69 | -rm -f $(BUILD_HOST) 70 | -rm -f *.o $(TARGET) 71 | 72 | .PHONY: all debug release build install clean 73 | -------------------------------------------------------------------------------- /statusbar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * statusbar.c 3 | * @author Alex Kozadaev (2015) 4 | */ 5 | 6 | #include "build_host.h" 7 | 8 | #define _DEFAULT_SOURCE 9 | #define _XOPEN_SOURCE 700 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifndef DEBUG 23 | #include 24 | #endif 25 | 26 | #define MIN(a, b) ((a) > (b) ? (b) : (a)) 27 | 28 | #define BUFSZ 64 29 | #define STATUSSZ 255 30 | #define MUTED 0 31 | 32 | /* Available statuses 33 | * 34 | * Charging 35 | * Draining 36 | * Unknown 37 | * Full 38 | */ 39 | typedef enum { CHARGING, DRAINING, UNKNOWN, FULL } battery_t; 40 | 41 | static void open_display(); 42 | static void close_display(); 43 | static void set_status(char *str); 44 | static const char *render_volume(int p); 45 | static size_t get_load_average(char *dst); 46 | static size_t get_datetime(char *dst); 47 | static int get_volume(void); 48 | static size_t read_str(const char *path, char *buf, size_t sz); 49 | 50 | #ifndef DEBUG 51 | static Display *dpy; 52 | #endif 53 | 54 | #ifdef BAT_STAT 55 | /* If battery exists - show the status and enable low battery action */ 56 | const bool SUSPEND_ON_LOW_BATTERY = false; 57 | const int THRESHOLD = 8; 58 | const int TIMEOUT = 40; 59 | const char *SUSPEND[] = {"/bin/sh", "/usr/local/bin/suspend.sh", NULL}; 60 | /* should be the same order as the battery_t enum */ 61 | const char CHARGE[] = {'+', '-', '?', '='}; 62 | 63 | static battery_t get_battery(void); 64 | static int read_int(const char *path); 65 | static void spawn(const char **params); 66 | #endif 67 | 68 | char *scale[] = {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; 69 | 70 | int main(int argc, char **argv) { 71 | int vol = 0; 72 | 73 | #ifdef BAT_STAT 74 | int timer = 0; 75 | float charge; /* battery charge */ 76 | battery_t bstat; /* battery status */ 77 | #endif 78 | 79 | char lnk[BUFSZ] = {0}; /* wifi link */ 80 | char la[BUFSZ] = {0}; /* load average */ 81 | char dt[BUFSZ] = {0}; /* date/time */ 82 | char stat[STATUSSZ] = {0}; /* full string */ 83 | 84 | if (argc > 1 && strcmp(argv[1], "-v") == 0) { 85 | printf("dwm-statusbar v%s" 86 | #ifdef DEBUG 87 | " (debug)" 88 | #endif 89 | " [%s %s]\n\nUsage: %s [-v]\n\n", 90 | BUILD_VERSION, BUILD_OS, BUILD_KERNEL, argv[0]); 91 | exit(0); 92 | } 93 | 94 | open_display(); 95 | 96 | while (!sleep(1)) { 97 | vol = get_volume(); 98 | get_load_average(la); 99 | read_str(LNK_PATH, lnk, BUFSZ); /* link status */ 100 | get_datetime(dt); /* date/time */ 101 | 102 | #ifdef BAT_STAT 103 | charge = ((float)read_int(BAT_NOW) / read_int(BAT_FULL)) * 104 | 100.0f; /* battery charge percent */ 105 | 106 | /* battery status (charging/discharging/full/etc) */ 107 | bstat = get_battery(); 108 | 109 | if (SUSPEND_ON_LOW_BATTERY && bstat == DRAINING && charge < THRESHOLD) { 110 | snprintf(stat, STATUSSZ, "LOW BATTERY: suspending after %d ", 111 | TIMEOUT - timer); 112 | 113 | if (timer >= TIMEOUT) { 114 | spawn(SUSPEND); 115 | timer = 0; 116 | } else { 117 | timer++; 118 | } 119 | } else { 120 | snprintf(stat, STATUSSZ, "%s | vol:%s | wifi:%s | bat:%c%0.1f%% | %s", la, 121 | render_volume(vol), lnk, CHARGE[bstat], MIN(charge, 100), dt); 122 | timer = 0; /* reseting the standby timer */ 123 | } 124 | #else 125 | snprintf(stat, STATUSSZ, "%s | vol:%s | wifi:%s | %s", la, 126 | render_volume(vol), lnk, dt); 127 | #endif 128 | 129 | set_status(stat); 130 | } 131 | 132 | close_display(); 133 | 134 | return 0; 135 | } 136 | 137 | void open_display() { 138 | #ifndef DEBUG 139 | if (!(dpy = XOpenDisplay(NULL))) { 140 | exit(1); 141 | } 142 | #endif 143 | signal(SIGINT, close_display); 144 | signal(SIGTERM, close_display); 145 | } 146 | 147 | void close_display() { 148 | #ifndef DEBUG 149 | XCloseDisplay(dpy); 150 | #endif 151 | exit(0); 152 | } 153 | 154 | void set_status(char *str) { 155 | #ifndef DEBUG 156 | XStoreName(dpy, DefaultRootWindow(dpy), str); 157 | XSync(dpy, False); 158 | #else 159 | puts(str); 160 | #endif 161 | } 162 | 163 | const char *render_volume(int p) { 164 | if (p < 0) { 165 | return "M"; 166 | } 167 | 168 | return scale[(p * 7) / 100]; 169 | } 170 | 171 | size_t get_load_average(char *dst) { 172 | double avgs[3]; 173 | 174 | if (getloadavg(avgs, 3) < 0) { 175 | set_status("getloadavg"); 176 | exit(1); 177 | } 178 | 179 | return sprintf(dst, "%.2f %.2f %.2f", avgs[0], avgs[1], avgs[2]); 180 | } 181 | 182 | size_t get_datetime(char *dst) { 183 | time_t rawtime = time(NULL); 184 | return strftime(dst, BUFSZ, "%a %b %d %H:%M:%S", localtime(&rawtime)); 185 | } 186 | 187 | int get_volume(void) { 188 | long min, max, volume = 0; 189 | int status = !MUTED; 190 | snd_mixer_t *handle; 191 | snd_mixer_selem_id_t *sid; 192 | const char *card = "default"; 193 | const char *selem_name = "Master"; 194 | 195 | snd_mixer_open(&handle, 0); 196 | snd_mixer_attach(handle, card); 197 | snd_mixer_selem_register(handle, NULL, NULL); 198 | snd_mixer_load(handle); 199 | 200 | snd_mixer_selem_id_alloca(&sid); 201 | snd_mixer_selem_id_set_index(sid, 0); 202 | snd_mixer_selem_id_set_name(sid, selem_name); 203 | snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); 204 | 205 | snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &status); 206 | 207 | if (status == MUTED) { 208 | return -1; 209 | } 210 | 211 | snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 212 | snd_mixer_selem_get_playback_volume(elem, 0, &volume); 213 | snd_mixer_close(handle); 214 | 215 | return ((double)volume / max) * 100; 216 | } 217 | 218 | size_t read_str(const char *path, char *buf, size_t sz) { 219 | FILE *fh; 220 | char ch = 0; 221 | size_t idx = 0; 222 | 223 | if (!(fh = fopen(path, "r"))) { 224 | return idx; 225 | } 226 | 227 | while ((ch = fgetc(fh)) != EOF && ch != '\0' && ch != '\n' && idx < sz) { 228 | buf[idx++] = ch; 229 | } 230 | 231 | buf[idx] = '\0'; 232 | fclose(fh); 233 | 234 | return idx; 235 | } 236 | 237 | #ifdef BAT_STAT 238 | 239 | battery_t get_battery(void) { 240 | FILE *bs; 241 | char st; 242 | 243 | if ((bs = fopen(BAT_STAT, "r")) == NULL) { 244 | return UNKNOWN; 245 | } 246 | 247 | st = fgetc(bs); 248 | fclose(bs); 249 | 250 | switch (tolower(st)) { 251 | case 'c': 252 | return CHARGING; 253 | 254 | case 'd': 255 | return DRAINING; 256 | 257 | case 'i': /* Idle - fall through */ 258 | case 'f': 259 | return FULL; 260 | 261 | default: 262 | return UNKNOWN; 263 | } 264 | } 265 | 266 | int read_int(const char *path) { 267 | char buf[BUFSZ] = {0}; 268 | read_str(path, buf, BUFSZ); 269 | return atoi(buf); 270 | } 271 | 272 | void spawn(const char **params) { 273 | #ifndef DEBUG 274 | if (fork() == 0) { 275 | setsid(); 276 | execv(params[0], (char **)params); 277 | exit(0); 278 | } 279 | #else 280 | printf("spawning command %s %s\n", params[0], params[1]); 281 | #endif 282 | } 283 | 284 | #endif 285 | --------------------------------------------------------------------------------