ztatus

Status bar for dwm, and simple notification daemon.
git clone git://git.noxz.tech/ztatus
Log | Files | Refs | LICENSE

commit 757467be124d0a86dc5d8709c565448b1afea434
parent cd71558e90e3c5640954e704f50d2d10362ac2ac
Author: Chris Noxz <chris@noxz.tech>
Date:   Thu,  2 May 2019 18:33:58 +0200

use fifo instead of signals

Diffstat:
MMakefile | 19+++++++++++--------
Mconfig.def.h | 62+++++++++++++++++++++++++++++++++++++++++---------------------
Mztatus.c | 354++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Aztatusc | 50++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 301 insertions(+), 184 deletions(-)

diff --git a/Makefile b/Makefile @@ -33,19 +33,22 @@ clean: @rm -f ztatus $(OBJ) install: ztatus - @echo installing executable to ${PREFIX}/bin - @mkdir -p $(PREFIX)/bin - @cp -f ztatus $(PREFIX)/bin - @chmod 755 $(PREFIX)/bin/ztatus + @echo installing executables to ${PREFIX}/bin + @mkdir -p ${PREFIX}/bin + @cp -f ztatus ${PREFIX}/bin + @cp -f ztatusc ${PREFIX}/bin + @chmod 755 ${PREFIX}/bin/ztatus + @chmod 755 ${PREFIX}/bin/ztatusc @echo installing manual page to ${MANPREFIX}/man1 @mkdir -p ${MANPREFIX}/man1 @cp -f ztatus.1 ${MANPREFIX}/man1 @chmod 644 ${MANPREFIX}/man1/ztatus.1 uninstall: - @echo removing executable file from ${DESTDIR}${PREFIX}/bin - @rm -f ${DESTDIR}${PREFIX}/bin/ztatus - @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 - @rm -f ${DESTDIR}${MANPREFIX}/man1/ztatus.1 + @echo removing executable files from ${PREFIX}/bin + @rm -f ${PREFIX}/bin/ztatus + @rm -f ${PREFIX}/bin/ztatusc + @echo removing manual page from ${MANPREFIX}/man1 + @rm -f ${MANPREFIX}/man1/ztatus.1 .PHONY: all options clean install uninstall diff --git a/config.def.h b/config.def.h @@ -1,14 +1,9 @@ -#define NOTIFICATION_DIR "~/.ztatus" -#define NOTIFICATION_PATH "~/.ztatus/notification" -#define MAIL_DIR "~/mail/webmail/INBOX/new" +#define FIFO_PATH "/tmp/ztatus.fifo" +#define MAIL_DIR_0 "~/mail/webmail-0-local/INBOX/new" +#define MAIL_DIR_1 "~/mail/webmail-1-local/INBOX/new" #define UPDATES_CMD "pkg queue | wc -l" -#define MIN_INTERVAL 1 // Minumum update time (seconds) -#define DELAY_INTERVAL 5 // Seconds of delay when initialized -#define SIG_VOLUME 35 // RTMIN+1 -#define SIG_MAIL 36 // RTMIN+2 -#define SIG_UPDATES 37 // RTMIN+3 -#define SIG_DELAY 40 // RTMIN+6 -#define SIG_NOTIFY 41 // RTMIN+7 +#define MIN_INTERVAL 1 +#define DELAY_INTERVAL 5 #define FRMT_VOLUME "%s\ue00a\x07%2d%% " #define FRMT_POWER "\x09%s\x07%2d%% " @@ -54,15 +49,40 @@ static Limit limit_updates[] = { }; static Element elements[ELEMENT_COUNT] = { - /* handler renderer format value arguments */ - { get_volume, render_volume, FRMT_VOLUME, -1, { .s = SIG_VOLUME } }, - { get_power, render_power, FRMT_POWER, -1, { .t = 1 } }, - { get_temp, render_temp, FRMT_TEMP, -1, { .t = 10 } }, - { get_cpu, render_cpu, FRMT_CPU, -1, { .t = 1 } }, - { get_memory, render_mem, FRMT_MEM, -1, { .t = 1 } }, - { get_mail, render_mail, FRMT_MAIL, -1, { .s = SIG_MAIL } }, - { get_updates, render_updates, FRMT_UPDATES, -1, { .s = SIG_UPDATES } }, - { get_date, render_date, FRMT_DATE, -1, { .t = -60 } }, - { get_time, render_time, FRMT_TIME, -1, { .t = -60 } }, - { NULL, NULL, FRMT_ICON, -1, { 0 } }, + /* handler renderer format val/vis arguments */ + [ElmVolume] = { get_volume, render_volume, FRMT_VOLUME, -1,1, {0} }, + [ElmPower] = { get_power, render_power, FRMT_POWER, -1,1, {.d = 1} }, + [ElmTemperature] = { get_temp, render_temp, FRMT_TEMP, -1,1, {.d = 10} }, + [ElmCPU] = { get_cpu, render_cpu, FRMT_CPU, -1,1, {.d = 1} }, + [ElmMemory] = { get_memory, render_mem, FRMT_MEM, -1,1, {.d = 1} }, + [ElmMail0] = { get_mail, render_mail, FRMT_MAIL, -1,1, {.t = MAIL_DIR_0} }, + [ElmMail1] = { get_mail, render_mail, FRMT_MAIL, -1,1, {.t = MAIL_DIR_1} }, + [ElmUpdates] = { get_updates, render_updates, FRMT_UPDATES, -1,1, {0} }, + [ElmDate] = { get_date, render_date, FRMT_DATE, -1,0, {.d = -60} }, + [ElmTime] = { get_time, render_time, FRMT_TIME, -1,0, {.d = -60} }, + [ElmIcon] = { NULL, NULL, FRMT_ICON, -1,1, {0} }, +}; + +static Command commands[] = { + /* --- toggles ----------------------------------------------------------*/ + { "toggle volume", toggle_element, {.v = &elements[ElmVolume]} }, + { "toggle power", toggle_element, {.v = &elements[ElmPower]} }, + { "toggle temperature", toggle_element, {.v = &elements[ElmTemperature]} }, + { "toggle cpu", toggle_element, {.v = &elements[ElmCPU]} }, + { "toggle memory", toggle_element, {.v = &elements[ElmMemory]} }, + { "toggle mail0", toggle_element, {.v = &elements[ElmMail0]} }, + { "toggle mail1", toggle_element, {.v = &elements[ElmMail1]} }, + { "toggle updates", toggle_element, {.v = &elements[ElmUpdates]} }, + { "toggle date", toggle_element, {.v = &elements[ElmDate]} }, + { "toggle time", toggle_element, {.v = &elements[ElmTime]} }, + { "toggle icon", toggle_element, {.v = &elements[ElmIcon]} }, + + /* --- updates ----------------------------------------------------------*/ + { "update volume", update_element, {.v = &elements[ElmVolume]} }, + { "update mail0", update_element, {.v = &elements[ElmMail0]} }, + { "update mail1", update_element, {.v = &elements[ElmMail1]} }, + { "update updates", update_element, {.v = &elements[ElmUpdates]} }, + + /* --- miscellaneous ----------------------------------------------------*/ + { "notify ...", notify, {0} }, }; diff --git a/ztatus.c b/ztatus.c @@ -4,6 +4,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/select.h> #include <sys/stat.h> #include <time.h> #include <unistd.h> @@ -14,20 +15,43 @@ #include <alsa/mixer.h> #define PNAME "ztatus" -#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) -#define ELEMENT_COUNT 10 +#define ELEMENT_COUNT 11 +#define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define LENGTH(x) (sizeof (x) / sizeof (x[0])) +#define STATUS_LENGTH 256 + +enum { + ElmVolume, + ElmPower, + ElmTemperature, + ElmCPU, + ElmMemory, + ElmMail0, + ElmMail1, + ElmUpdates, + ElmDate, + ElmTime, + ElmIcon +}; typedef struct { - int s; /* signal */ - int t; /* time delay */ + int d; /* time delay */ + const char *t; /* text */ +} Attr; + +typedef struct { + int i; + const void *v; } Arg; typedef struct { - int (*handler)(int*); - void (*renderer)(char*, const char*, int); + int (*handler)(int*, const Attr*); + void (*renderer)(char*, const char*, int); const char *format; int value; - const Arg arg; + int visible; + const Attr attr; } Element; typedef struct { @@ -35,28 +59,32 @@ typedef struct { const char *symbol; } Limit; -/* X11 display */ -static Display *dpy; +typedef struct { + const char *name; + void (*func)(const Arg *, const char*, char*); + const Arg arg; +} Command; -/* resource holders */ +/* global variables */ +static Display *dpy; static long cpu_work[2][2] = {{-1}, {-1}}; - -/* flags, and miscellaneous */ static int is_changed = 0; static int delay_time = 0; static int running = 0; static int self_pid = 0; -static char notification[128] = {0}; - -static int get_volume(int*); -static int get_power(int*); -static int get_temp(int*); -static int get_cpu(int*); -static int get_memory(int*); -static int get_mail(int*); -static int get_updates(int*); -static int get_date(int*); -static int get_time(int*); +static char *status_line = NULL; +static int fifofd; + +/* functions referenced in config */ +static int get_volume(int*, const Attr*); +static int get_power(int*, const Attr*); +static int get_temp(int*, const Attr*); +static int get_cpu(int*, const Attr*); +static int get_memory(int*, const Attr*); +static int get_mail(int*, const Attr*); +static int get_updates(int*, const Attr*); +static int get_date(int*, const Attr*); +static int get_time(int*, const Attr*); static void render_volume(char*, const char*, int); static void render_power(char*, const char*, int); @@ -68,25 +96,26 @@ static void render_updates(char*, const char*, int); static void render_date(char*, const char*, int); static void render_time(char*, const char*, int); +static void toggle_element(const Arg*, const char*, char*); +static void update_element(const Arg*, const char*, char*); +static void notify(const Arg*, const char*, char*); + #include "config.h" /* functions */ static int check_proc(struct dirent*); static int count_until_time(void); static int daemonize(void); -static void delay_handler(int); -static void element_handler(int); static int expand_tilde(const char*, char**); static int get_int(const char*, int*); static int get_pid(void); -static int notify(const char*); -static void notify_handler(int); static void render_threshold(char*, const char*, int, Limit[], int); static void render_threshold_start(char*, const char*, int, Limit[], int, int); static int run(const char*); static void set_status(char*); static void sigint_handler(int); -static void usage(void); +static int list_commands(void); +static int usage(void); void sigint_handler(int signum) @@ -95,92 +124,105 @@ sigint_handler(int signum) } void -delay_handler(int signum) -{ - delay_time = DELAY_INTERVAL; -} - -void -element_handler(int signum) +dispatchcmd(void) { + char buf[BUFSIZ]; + char *ptr, *line, *next; + ssize_t n; int i; - for (i = 0; i < ELEMENT_COUNT; i++) { - if (elements[i].arg.s) - is_changed |= elements[i].handler(&(elements[i].value)); - } -} -void -notify_handler(int signum) -{ - char *path = NULL; - FILE *fptr = NULL; - - expand_tilde(NOTIFICATION_PATH, &path); - - if (path == NULL) + if ((n = read(fifofd, buf, sizeof(buf) - 1)) == -1) return; - if ((fptr = fopen(path, "rb")) != NULL) { - fread(notification, sizeof(notification), 1, fptr); - notification[ftell(fptr)] = '\0'; - fclose(fptr); - remove(path); - - if (strlen(notification) > 0) - delay_time = DELAY_INTERVAL; + buf[n] = '\0'; + line = buf; + + /* read each line as a single command */ + while (line) { + next = strchr(line, '\n'); + if (next) + *next = '\0'; + for (i = 0; i < LENGTH(commands); i++) { + /* check if command has a trailing argument */ + if ((ptr = strstr(commands[i].name, "..."))) + n = ptr - commands[i].name; + else + n = MAX(strlen(line), strlen(commands[i].name)); + if (strncmp(commands[i].name, line, n) == 0) { + commands[i].func(&commands[i].arg, commands[i].name, line); + break; + } + } + if (next) + *next = '\n'; + line = next ? next + 1 : NULL; } - free(path); + /* make sure fifo is empty */ + while (errno != EWOULDBLOCK) + read(fifofd, buf, sizeof(buf) - 1); } int daemonize(void) { - char *status_line; - int i, count = 0; + int i, rv, count; + fd_set rfds; + struct timeval tv; + unlink(FIFO_PATH); + + /* setup essentials */ if (get_pid() != -1) return 1; - + if ((mknod(FIFO_PATH, S_IFIFO | 0600, 0)) < 0) + return 1; + if ((fifofd = open(FIFO_PATH, O_RDWR | O_NONBLOCK)) < 0) + return 1; if (!(dpy = XOpenDisplay(NULL))) return 1; - - if ((status_line = malloc(256)) == NULL) + if ((status_line = malloc(STATUS_LENGTH)) == NULL) return 1; - signal(SIG_DELAY, delay_handler); - signal(SIG_NOTIFY, notify_handler); - signal(SIGINT, sigint_handler); - signal(SIGTERM, sigint_handler); + /* handle interupts and terminations */ + signal(SIGINT, sigint_handler); + signal(SIGTERM, sigint_handler); /* initialize elements, and bind signal handlers */ - for (i = 0; i < ELEMENT_COUNT; i++) { - if (elements[i].arg.s) - signal(elements[i].arg.s, element_handler); + for (i = 0; i < LENGTH(elements); i++) { if (elements[i].handler) - elements[i].handler(&(elements[i].value)); + elements[i].handler(&(elements[i].value), &(elements[i].attr)); } /* get time delay until next full minute. * this will initially sync the time loop */ - count = -count_until_time(); + count = -count_until_time() - 1; + running = 1; - /* TODO :: count will get out of sync as the loop isn't instant and the - * sleep is constant. should this get fixed using a resync after n minutes? - */ - for (running = 1, is_changed = 1; running; sleep(MIN_INTERVAL), count++) { + while (running) { + /* resync count after 5 minutes, as it may shift + * due to various things */ + if (count >= 300) + count = 60 - count_until_time(); - /* check for notifications */ - if (notification[0] != 0) { - snprintf(status_line, 256, "\x0a%s", notification); - set_status(status_line); - notification[0] = 0; - is_changed = 1; - } + count++; + + FD_ZERO(&rfds); + FD_SET(fifofd, &rfds); - /* prevent data printing if it's delayed */ - /* this is so an external signal can borrow the status line + tv.tv_sec = MIN_INTERVAL; + tv.tv_usec = 0; + + /* select fifo descriptor, and break on failure */ + if ((rv = select(fifofd + 1, &rfds, NULL, NULL, &tv)) < 0) + break; + + /* dispatch command on fifo read */ + if (FD_ISSET(fifofd, &rfds)) + dispatchcmd(); + + /* prevent data printing if it's delayed. + * this is so an external signal can borrow the status line * for a short time, determined by the delay_time */ if (delay_time > 0) { delay_time--; @@ -188,25 +230,28 @@ daemonize(void) } /* handle time based elements */ - for (i = 0; i < ELEMENT_COUNT; i++) { + for (i = 0; i < LENGTH(elements); i++) { if (!elements[i].handler) continue; - if (elements[i].arg.t && ( - (elements[i].arg.t > 0 && count % elements[i].arg.t == 0) - || (elements[i].arg.t < 0 && count >= 0 - && count % elements[i].arg.t == 0) + if (elements[i].attr.d && ( + (elements[i].attr.d > 0 && count % elements[i].attr.d == 0) + || (elements[i].attr.d < 0 && count >= 0 + && count % elements[i].attr.d == 0) )) - is_changed |= elements[i].handler(&(elements[i].value)); + is_changed |= elements[i].handler( + &(elements[i].value), + &(elements[i].attr)); } + /* render all elements on any change */ if (is_changed) { status_line[0] = '\0'; - - /* render all elements */ - for (i = 0; i < ELEMENT_COUNT; i++) { + for (i = 0; i < LENGTH(elements); i++) { + if (!elements[i].visible) + continue; if (elements[i].renderer) elements[i].renderer( - status_line, + status_line, elements[i].format, elements[i].value); else @@ -217,12 +262,13 @@ daemonize(void) set_status(status_line); } - } /* free up resources when done */ free(status_line); XCloseDisplay(dpy); + if (fifofd >= 0) + unlink(FIFO_PATH); return 0; } @@ -367,47 +413,7 @@ get_pid(void) } int -notify(const char *msg) -{ - int pid; - FILE *fptr = NULL; - struct stat sb; - char *dir = NULL; - char *path = NULL; - int state = 0; - - expand_tilde(NOTIFICATION_DIR, &dir); - expand_tilde(NOTIFICATION_PATH, &path); - - if (state == 0 && (path == NULL || dir == NULL)) - state = 1; - - if (state == 0 && (pid = get_pid()) == -1) - state = 1; - - if (state == 0 && stat(dir, &sb) == -1) - mkdir(dir, 0700); - - if (state == 0 && (fptr = fopen(path, "w+")) == NULL) - state = 1; - - if (state == 0) { - fwrite(msg, 1, strlen(msg), fptr); - fclose(fptr); - - kill(pid, SIG_NOTIFY); - } - - if (dir != NULL) - free(dir); - if (path != NULL) - free(path); - - return state; -} - -int -get_volume(int *value) +get_volume(int *value, const Attr *attr) { snd_mixer_t *handle; snd_mixer_selem_id_t *sid; @@ -438,7 +444,7 @@ get_volume(int *value) } int -get_power(int *value) +get_power(int *value, const Attr *attr) { FILE *fp; int energy_now, energy_full, voltage_now; @@ -480,7 +486,7 @@ get_power(int *value) } int -get_temp(int *value) +get_temp(int *value, const Attr *attr) { FILE *fp; double res, val = 0; @@ -509,7 +515,7 @@ get_temp(int *value) } int -get_memory(int *value) +get_memory(int *value, const Attr *attr) { FILE *fp; long ma[5]; @@ -537,7 +543,7 @@ get_memory(int *value) } int -get_cpu(int *value) +get_cpu(int *value, const Attr *attr) { FILE *fp; long la[7]; @@ -579,7 +585,7 @@ get_cpu(int *value) } int -get_mail(int *value) +get_mail(int *value, const Attr *attr) { char *dir = NULL; struct dirent *dp; @@ -587,7 +593,7 @@ get_mail(int *value) int old_value = *value; *value = 0; - if (!expand_tilde(MAIL_DIR, &dir)) + if (!expand_tilde(attr->t, &dir)) return 0; fd = opendir(dir); @@ -609,7 +615,7 @@ get_mail(int *value) } int -get_updates(int *value) +get_updates(int *value, const Attr *attr) { int old_value = *value; @@ -622,7 +628,7 @@ get_updates(int *value) } int -get_date(int *value) +get_date(int *value, const Attr *attr) { time_t rawtime; struct tm *timeinfo; @@ -639,7 +645,7 @@ get_date(int *value) } int -get_time(int *value) +get_time(int *value, const Attr *attr) { time_t rawtime; struct tm *timeinfo; @@ -796,11 +802,50 @@ set_status(char *value) { } void +toggle_element(const Arg *arg, const char* cmd, char* input) +{ + Element *e = ((Element *)arg->v); + e->visible ^= 1; + is_changed = 1; +} + +void +update_element(const Arg *arg, const char* cmd, char* input) +{ + Element *e = ((Element *)arg->v); + is_changed |= e->handler(&(e->value), &(e->attr)); +} + +void +notify(const Arg *arg, const char* cmd, char* input) +{ + int l = strlen(cmd) - 3; + + if (l <= 0 || strlen(input) <= l) + return; + + delay_time = (int)(DELAY_INTERVAL / MIN_INTERVAL); + + snprintf(status_line, STATUS_LENGTH - 1, "\x0a%s", input + l); + set_status(status_line); +} + +int +list_commands(void) +{ + int i; + for (i = 0; i < LENGTH(commands); i++) + fprintf(stdout, "%s\n", commands[i].name); + return 0; +} + +int usage(void) { - fprintf(stderr, "usage: %s [-d] [-n TEXT]\n", PNAME); - fprintf(stderr, " -d daemonize\n"); - fprintf(stderr, " -n TEXT send notification\n"); + fprintf(stderr, "usage: %s [-d] [-l]\n", PNAME); + fprintf(stderr, " -d daemonize\n"); + fprintf(stderr, " -l list commands\n"); + return 0; } int @@ -808,7 +853,7 @@ main(int argc, char *argv[]) { char *s; self_pid = getpid(); - + if ((s = strrchr(argv[0], '/')) == NULL) s = argv[0]; else @@ -816,9 +861,8 @@ main(int argc, char *argv[]) if (argc == 2 && strcmp(argv[1], "-d") == 0 && strcmp(s, PNAME) == 0) return daemonize(); - else if (argc == 3 && strcmp(argv[1], "-n") == 0) - return notify(argv[2]); - - usage(); - return 0; + else if (argc == 2 && strcmp(argv[1], "-l") == 0) + return list_commands(); + else + return usage(); } diff --git a/ztatusc b/ztatusc @@ -0,0 +1,50 @@ +#!/bin/sh + +ztatusfifo="/tmp/ztatus.fifo" + +send_toggle() { + send_command "toggle $1" +} + +send_update() { + send_command "update $1" +} + +send_notification() { + send_command "notify $1" +} + +send_command() { + printf '%s' "$1" > "$ztatusfifo" +} + +case "$1" in +toggle) + elements="$(ztatus -l | grep "^toggle " | awk '{ print $2 }')" + case "$2" in + datetime) + send_command "$(printf 'toggle date\ntoggle time')" ;; + *) + if [ "$(echo "$elements" | grep "^$2$")" ]; then + send_toggle "$2" + else + echo "Unknown element: $2" + fi ;; + esac ;; +update) + elements="$(ztatus -l | grep "^update " | awk '{ print $2 }')" + case "$2" in + mail) + send_command "$(printf 'update mail0\nupdate mail1')" ;; + *) + if [ "$(echo "$elements" | grep "^$2$")" ]; then + send_update "$2" + else + echo "Unknown element: $2" + fi ;; + esac ;; +notify) + shift; send_notification "$*" ;; +*) + echo "Unknown option: $*" ;; +esac