ztatus

[discontinued] Status bar for dwm, and simple notification daemon.
git clone https://noxz.tech/git/ztatus.git
ztatus

commit: b83fee8c437e333a3dafe030e60b35d86ae8bfb9
parent: 
author: Chris Noxz <chris@noxz.tech>
date:   Thu, 3 Jan 2019 14:06:08 +0100
Initial commit
A.gitignore3+
ALICENSE21+
AMakefile51++
Aconfig.def.h68++
Aconfig.mk22+
Aztatus.136+
Aztatus.c819++++++++++++++++++++
7 files changed, 1020 insertions(+)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+config.h
+ztatus
+ztatus.o
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+© 2018-2019 Chris Noxz <chris@noxz.tech>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,51 @@
+.POSIX:
+
+include config.mk
+
+SRC = ztatus.c
+OBJ = $(SRC:.c=.o)
+
+all: options ztatus
+
+options:
+	@echo ztatus build options:
+	@echo "VERSION = $(VERSION)"
+	@echo "CFLAGS  = $(STCFLAGS)"
+	@echo "LDFLAGS = $(STLDFLAGS)"
+	@echo "CC      = $(CC)"
+
+.c.o:
+	@echo CC $<
+	$(CC) $(STCFLAGS) -c $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+	@echo creating $@ from config.def.h
+	@cp config.def.h $@
+
+ztatus: $(OBJ)
+	@echo CC -o $@
+	$(CC) -o $@ $(OBJ) $(STLDFLAGS)
+
+clean:
+	@echo cleaning
+	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 manual page to ${MANPREFIX}/man1
+	mkdir -p ${MANPREFIX}/man1
+	sed "s/VERSION/${VERSION}/g" < ztatus.1 > ${MANPREFIX}/man1/ztatus.1
+	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
+
+.PHONY: all options clean install uninstall
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,68 @@
+#define NOTIFICATION_DIR    "~/.ztatus"
+#define NOTIFICATION_PATH   "~/.ztatus/notification"
+#define MAIL_DIR            "~/mail/webmail/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 FRMT_VOLUME         "%s\ue00a\x07%2d%%  "
+#define FRMT_POWER          "\x09%s\x07%2d%%  "
+#define FRMT_TEMP           "\x07\ue00c%s%2d°C  "
+#define FRMT_CPU            "\x07\ue015%s%2d%%  "
+#define FRMT_MEM            "\x07\ue016%s%2d%%  "
+#define FRMT_MAIL           "%s\ue00d\x07%d  "
+#define FRMT_UPDATES        "%s\ue00e\x07%d  "
+#define FRMT_DATE           "\x07\ue00f\x07%04u-%02u-%02u  "
+#define FRMT_TIME           "\x09\ue010\x07%02u:%02u  "
+#define FRMT_ICON           "\x02\ue000"
+
+static Limit limit_volume[] = {
+    {1, "\x08"},
+    {0, "\x07"}
+};
+static Limit limit_power[] = {
+    {75, "\ue011"},
+    {50, "\ue012"},
+    {25, "\ue013"},
+    {0, "\ue014"},
+    {0, "\ue00b"}
+};
+static Limit limit_temp[] = {
+    {60, "\x09"},
+    {0, "\x07"}
+};
+static Limit limit_cpu[] = {
+    {75, "\x09"},
+    {0, "\x07"}
+};
+static Limit limit_mem[] = {
+    {80, "\x09"},
+    {0, "\x07"}
+};
+static Limit limit_mail[] = {
+    {1, "\x08"},
+    {0, "\x07"}
+};
+static Limit limit_updates[] = {
+    {1, "\x08"},
+    {0, "\x07"}
+};
+
+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 } },
+};
diff --git a/config.mk b/config.mk
@@ -0,0 +1,22 @@
+# ztatus version
+VERSION = 0.1.1
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+ASOUND = /usr/include/alsa
+
+# includes and libs
+INCS = -I$(X11INC) -I$(ASOUND)
+LIBS = -L$(X11LIB) -lX11 -lasound -lz
+
+# flags
+CFLAGS = -Wall -pedantic -std=c99
+CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600
+STCFLAGS = $(INCS) $(CPPFLAGS) $(CFLAGS)
+STLDFLAGS = $(LIBS) $(LDFLAGS)
+
+# compiler and linker
+CC = gcc
diff --git a/ztatus.1 b/ztatus.1
@@ -0,0 +1,36 @@
+.Dd $Mdocdate$
+.Dt ZTATUS 1
+.Os
+.Sh NAME
+.Nm ztatus
+.Nd creates a status bar for dwm, and also acts as a simple notification
+daemon.
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl n Ar text
+.Sh DESCRIPTION
+.Nm
+creates a status bar for dwm, and other window managers using the root WM_NAME
+as input for displaying a status bar. Except for creating a status bar,
+.Nm
+also acts as a simple notification daemon.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl d
+Specifies that
+.Nm
+should run continuously as a daemon.
+.It Fl n Ar text
+Sends a notification with the value of
+.Ar text
+to a running
+.Nm
+daemon.
+.El
+.Sh CUSTOMIZATION
+.Nm
+can be customized by creating a custom config.h and (re)compiling the source
+code.
+.Sh SEE ALSO
+.Xr dwm 1
diff --git a/ztatus.c b/ztatus.c
@@ -0,0 +1,819 @@
+#include <dirent.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+
+/* alsa */
+#include <alsa/asoundlib.h>
+#include <alsa/mixer.h>
+
+#define PNAME               "ztatus"
+#define LIMIT(x, a, b)      (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
+#define ELEMENT_COUNT       10
+
+typedef struct {
+    int s;      /* signal */
+    int t;      /* time delay */
+} Arg;
+
+typedef struct {
+    int (*handler)(int*); 
+    void (*renderer)(char*, const char*, int); 
+    const char *format;
+    int value;
+    const Arg arg;
+} Element;
+
+typedef struct {
+    int threshold;
+    const char *symbol;
+} Limit;
+
+/* X11 display */
+static Display *dpy;
+
+/* resource holders */
+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 void render_volume(char*, const char*, int);
+static void render_power(char*, const char*, int);
+static void render_temp(char*, const char*, int);
+static void render_cpu(char*, const char*, int);
+static void render_mem(char*, const char*, int);
+static void render_mail(char*, const char*, int);
+static void render_updates(char*, const char*, int);
+static void render_date(char*, const char*, int);
+static void render_time(char*, const char*, int);
+
+#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);
+
+void
+sigint_handler(int signum)
+{
+    running = 0;
+}
+
+void
+delay_handler(int signum)
+{
+    delay_time = DELAY_INTERVAL;
+}
+
+void
+element_handler(int signum)
+{
+    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)
+        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;
+    }
+
+    free(path);
+}
+
+int
+daemonize(void)
+{
+    char *status_line;
+    int i, count = 0;
+
+    if (get_pid() != -1)
+        return 1;
+
+    if (!(dpy = XOpenDisplay(NULL)))
+        return 1;
+
+    if ((status_line = malloc(256)) == NULL)
+        return 1;
+
+    signal(SIG_DELAY,   delay_handler);
+    signal(SIG_NOTIFY,  notify_handler);
+    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);
+        if (elements[i].handler)
+            elements[i].handler(&(elements[i].value));
+    }
+
+    /* get time delay until next full minute.
+     * this will initially sync the time loop */
+    count = -count_until_time();
+
+    /* 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++) {
+
+        /* check for notifications */
+        if (notification[0] != 0) {
+            snprintf(status_line, 256, "\x0a%s", notification);
+            set_status(status_line);
+            notification[0] = 0;
+            is_changed = 1;
+        }
+
+        /* 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--;
+            continue;
+        }
+
+        /* handle time based elements */
+        for (i = 0; i < ELEMENT_COUNT; 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)
+            ))
+                is_changed |= elements[i].handler(&(elements[i].value));
+        }
+
+        if (is_changed) {
+            status_line[0] = '\0';
+
+            /* render all elements */
+            for (i = 0; i < ELEMENT_COUNT; i++) {
+                if (elements[i].renderer)
+                    elements[i].renderer(
+                        status_line, 
+                        elements[i].format,
+                        elements[i].value);
+                else
+                    strcpy(
+                        status_line + strlen(status_line),
+                        elements[i].format);
+            }
+
+            set_status(status_line);
+        }
+
+    }
+
+    /* free up resources when done */
+    free(status_line);
+    XCloseDisplay(dpy);
+
+    return 0;
+}
+
+int
+expand_tilde(const char *dir, char **out)
+{
+    const char *home = getenv("HOME");
+
+    if (!(home && strchr(home, '/')))
+        return 0;
+
+    if (strchr(dir, '~') != NULL) {
+        *out = malloc(strlen(home) + strlen(dir));
+        strcpy(*out, home);
+        strcat(*out, dir + 1);
+    } else {
+        *out = malloc(strlen(dir) + 1);
+        strcpy(*out, dir);
+    }
+
+    if (*out)
+        return 1;
+    return 0;
+}
+
+int
+get_int(const char *s, int *i)
+{
+    const char *c = s;
+
+    *i = 0;
+    while (*c) {
+        if (*c < 48 || *c >= 58)
+            return 0;
+        *i = *i * 10 - 48 + *c;
+        c++;
+    }
+
+    return 1;
+}
+
+int
+check_proc(struct dirent *e)
+{
+    char *base_p = NULL;
+    char *comm_p = NULL;
+    char *cmdline_p = NULL;
+    char comm[7] = {0};
+    char cmdline[64] = {0};
+    FILE *fptr = NULL;
+    struct stat sb;
+    int i, l;
+    int pid;
+    int state = 0;
+
+    if (get_int(e->d_name, &pid) == 0 || pid == self_pid)
+        state = -1;
+
+    if (state == 0) {
+        base_p = malloc(strlen(e->d_name) + 7);
+        strcpy(base_p, "/proc/");
+        strcat(base_p, e->d_name);
+
+        if (stat(base_p, &sb) != 0 || !S_ISDIR(sb.st_mode))
+            state = -1;
+    }
+
+    if (state == 0) {
+        comm_p = malloc(strlen(base_p) + 6);
+        strcpy(comm_p, base_p);
+        strcat(comm_p, "/comm");
+
+        if (stat(comm_p, &sb) != 0 || !S_ISREG(sb.st_mode))
+            state = -1;
+    }
+
+    if (state == 0) {
+        cmdline_p = malloc(strlen(base_p) + 9);
+        strcpy(cmdline_p, base_p);
+        strcat(cmdline_p, "/cmdline");
+
+        if (stat(cmdline_p, &sb) != 0 || !S_ISREG(sb.st_mode))
+            state = -1;
+    }
+
+    if (state == 0) {
+        fptr = fopen(comm_p, "rb");
+        for (i = 0, l = sizeof(comm); i < l; i++)
+            comm[i] = fgetc(fptr);
+        fclose(fptr);
+        if (comm[6] != '\n') {
+            state = -1;
+        } else {
+            comm[6] = '\0';
+            if (strcmp(comm, PNAME) != 0)
+                state = -1;
+        }
+    }
+
+    if (state == 0) {
+        fptr = fopen(cmdline_p, "rb");
+        fread(cmdline, sizeof(cmdline), 1, fptr);
+        l = ftell(fptr);
+        fclose(fptr);
+        if (l - 1 > strlen(cmdline))
+            memmove(cmdline, cmdline + strlen(cmdline) + 1, l - strlen(cmdline));
+        if (strcmp(cmdline, "-d") == 0)
+            return pid;
+    }
+
+    if (base_p != NULL)
+        free(base_p);
+    if (comm_p != NULL)
+        free(comm_p);
+    if (cmdline_p != NULL)
+        free(cmdline_p);
+
+    return -1;
+}
+
+int
+get_pid(void)
+{
+    char path[] = "/proc";
+    DIR *dir;
+    struct dirent *e;
+    int p = -1;
+
+    if ((dir = opendir(path)) != NULL) {
+        while (
+            (e = readdir(dir)) != NULL
+            && (p = check_proc(e)) == -1
+        );
+        closedir(dir);
+    }
+
+    return p;
+}
+
+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)
+{
+    snd_mixer_t *handle;
+    snd_mixer_selem_id_t *sid;
+    const char *card = "default";
+    const char *selem_name = "Master";
+    long min, max, volume = 0;
+    int old_state = *value;
+
+    snd_mixer_open(&handle, 0);
+    snd_mixer_attach(handle, card);
+    snd_mixer_selem_register(handle, NULL, NULL);
+    snd_mixer_load(handle);
+
+    snd_mixer_selem_id_alloca(&sid);
+    snd_mixer_selem_id_set_index(sid, 0);
+    snd_mixer_selem_id_set_name(sid, selem_name);
+    snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
+
+    snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
+    snd_mixer_selem_get_playback_volume(elem, 0, &volume);
+    snd_mixer_close(handle);
+
+    *value = ((double)volume / max) * 100;
+
+    if (*value != old_state)
+        return 1;
+    return 0;
+}
+
+int
+get_power(int *value)
+{
+    FILE *fp;
+    int energy_now, energy_full, voltage_now;
+    char status;
+    int old_value = *value;
+    int old_state = *value >= 1000;
+
+    fp = fopen("/sys/class/power_supply/BAT0/energy_now", "r");
+    if (!fp)
+        return 0;
+    fscanf(fp, "%d", &energy_now);
+    fclose(fp);
+
+    fp = fopen("/sys/class/power_supply/BAT0/energy_full", "r");
+    if (!fp)
+        return 0;
+    fscanf(fp, "%d", &energy_full);
+    fclose(fp);
+
+    fp = fopen("/sys/class/power_supply/BAT0/voltage_now", "r");
+    if (!fp)
+        return 0;
+    fscanf(fp, "%d", &voltage_now);
+    fclose(fp);
+
+    fp = fopen("/sys/class/power_supply/BAT0/status", "r");
+    if (!fp)
+        return 0;
+    fscanf(fp, "%c", &status);
+    fclose(fp);
+
+    *value = ((float)energy_now * 1000 / (float)voltage_now) * 100 / ((float)energy_full * 1000 / (float)voltage_now);
+    *value += status != 'D' ? 1000 : 0;
+
+    if (*value != old_value || (*value >= 1000) != old_state)
+        return 1;
+    return 0;
+}
+
+int
+get_temp(int *value)
+{
+    FILE *fp;
+    double res, val = 0;
+    unsigned int i, max;
+    const char* hwmon_file[] = {
+        "/sys/class/hwmon/hwmon1/temp2_input",
+        "/sys/class/hwmon/hwmon1/temp3_input"
+    };
+    int old_value = *value;
+
+    for (i = 0, max = 2; i < max; i++) {
+        if ((fp = fopen(hwmon_file[i], "r")) == NULL)
+            return 0;
+
+        fscanf(fp, "%lf", &res);
+        fclose(fp);
+
+        val = ((i * val) + res) / (i + 1);
+    }
+
+    *value = (int)(val / 1000);
+
+    if (*value != old_value)
+        return 1;
+    return 0;
+}
+
+int
+get_memory(int *value)
+{
+    FILE *fp;
+    long ma[5];
+    int old_value = *value;
+
+    fp = fopen("/proc/meminfo", "r");
+    if (fp == NULL)
+        return 0;
+
+    fscanf(fp,
+        "MemTotal: %ld kB\n"
+        "MemFree: %ld kB\n"
+        "MemAvailable: %ld kB\n"
+        "Buffers: %ld kB\n"
+        "Cached: %ld kB\n"
+        , &ma[0], &ma[1], &ma[2], &ma[3], &ma[4]
+    );
+    fclose(fp);
+
+    *value = (int)(100 * ((ma[0] - ma[1]) - (ma[3] + ma[4])) / ma[0]);
+
+    if (*value != old_value)
+        return 1;
+    return 0;
+}
+
+int
+get_cpu(int *value)
+{
+    FILE *fp;
+    long la[7];
+    int old_value = *value;
+    int r = 0;
+
+    if ((fp = fopen("/proc/stat", "rb"))) {
+        r = fscanf(fp, "cpu %ld %ld %ld %ld %ld %ld %ld",
+            &la[0], &la[1], &la[2], &la[3], &la[4], &la[5], &la[6]
+        );
+        fclose(fp);
+    }
+
+    if (r != 7)
+        return 0;
+
+    cpu_work[0][0] = la[0] + la[1] + la[2] + la[5] + la[6];
+    cpu_work[0][1] = cpu_work[0][0] + la[3] + la[4];
+
+    if (cpu_work[0][1] == cpu_work[1][1])
+        return 0;
+
+    if (*value >= 0)
+        *value = 100 * \
+            (cpu_work[0][0] - cpu_work[1][0]) /\
+            (cpu_work[0][1] - cpu_work[1][1]);
+    else
+        *value = 0;
+
+    LIMIT(*value, 0, 100);
+
+    /* store work value for later use */
+    cpu_work[1][0] = cpu_work[0][0];
+    cpu_work[1][1] = cpu_work[0][1];
+
+    if (*value != old_value)
+        return 1;
+    return 0;
+}
+
+int
+get_mail(int *value)
+{
+    char *dir = NULL;
+    struct dirent *dp;
+    DIR *fd;
+    int old_value = *value;
+    *value = 0;
+
+    if (!expand_tilde(MAIL_DIR, &dir))
+        return 0;
+
+    fd = opendir(dir);
+    free(dir);
+
+    if (fd == NULL)
+        return 0;
+
+    while ((dp = readdir(fd)) != NULL) {
+        if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, ".."))
+            (*value)++;
+    }
+
+    closedir(fd);
+
+    if (*value != old_value)
+        return 1;
+    return 0;
+}
+
+int
+get_updates(int *value)
+{
+    int old_value = *value;
+
+    if ((*value = run(UPDATES_CMD)) == -1)
+        *value = old_value;
+
+    if (*value != old_value)
+        return 1;
+    return 0;
+}
+
+int
+get_date(int *value)
+{
+    time_t rawtime;
+    struct tm *timeinfo;
+
+    time(&rawtime);
+    timeinfo = localtime(&rawtime);
+
+    *value = ((timeinfo->tm_year + 1900) * 10000
+        + (timeinfo->tm_mon + 1) * 100
+        + timeinfo->tm_mday
+    );
+
+    return 1;
+}
+
+int
+get_time(int *value)
+{
+    time_t rawtime;
+    struct tm *timeinfo;
+
+    time(&rawtime);
+    timeinfo = localtime(&rawtime);
+
+    *value = timeinfo->tm_hour * 60 + timeinfo->tm_min;
+
+    return 1;
+}
+
+void
+render_threshold_start(char *str, const char *format, int value, Limit l[], int start, int count)
+{
+    char *tmp = NULL;
+    int i;
+    for (i = start; i < (start + count); i++) {
+        if (value >= l[i].threshold) {
+            tmp = malloc(16);
+            snprintf(tmp, 16, format, l[i].symbol, value);
+            break;
+        }
+    }
+
+    if (tmp) {
+        strcpy(str + strlen(str), tmp);
+        free(tmp);
+    }
+}
+
+void
+render_threshold(char *str, const char *format, int value, Limit l[], int count)
+{
+    render_threshold_start(str, format, value, l, 0, count);
+}
+
+void
+render_volume(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_volume, 2);
+}
+
+void
+render_power(char *str, const char *format, int value)
+{
+    char *nformat = malloc(strlen(format) + 1);
+    int start = 0;
+    int count = 4;
+
+    strcpy(nformat, format);
+    if (value >= 1000) {
+        value -= 1000;
+        count = 1;
+        start = 4;
+        nformat[0] = '\x08';
+    }
+
+    render_threshold_start(str, nformat, value, limit_power, start, count);
+
+    if (nformat)
+        free(nformat);
+}
+
+void
+render_temp(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_temp, 2);
+}
+
+void
+render_cpu(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_cpu, 2);
+}
+
+void
+render_mem(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_mem, 2);
+}
+
+void
+render_mail(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_mail, 2);
+}
+
+void
+render_updates(char *str, const char *format, int value)
+{
+    render_threshold(str, format, value, limit_updates, 2);
+}
+
+void
+render_date(char *str, const char *format, int value)
+{
+    char *tmp = malloc(20);
+
+    snprintf(tmp, 20, format
+        ,value / 10000
+        ,(value % 10000) / 100
+        ,(value % 10000) % 100);
+    strcpy(str + strlen(str), tmp);
+    free(tmp);
+}
+
+void
+render_time(char *str, const char *format, int value)
+{
+    char *tmp = malloc(16);
+
+    snprintf(tmp, 16, format, value / 60, value % 60);
+    strcpy(str + strlen(str), tmp);
+    free(tmp);
+}
+
+int
+run(const char* cmd)
+{
+    FILE *fp;
+    char rval[8];
+    int val;
+
+    fp = popen(cmd, "r");
+    if (fp == NULL)
+        return -1;
+
+    if (fgets(rval, sizeof(rval), fp) != NULL)
+        sscanf(rval, "%d", &val);
+
+    pclose(fp);
+
+    return val;
+}
+
+int
+count_until_time(void) {
+    time_t rawtime;
+    struct tm *timeinfo;
+
+    time(&rawtime);
+    timeinfo = localtime(&rawtime);
+
+    return 60 - timeinfo->tm_sec;
+}
+
+void
+set_status(char *value) {
+    XStoreName(dpy, DefaultRootWindow(dpy), value);
+    XSync(dpy, False);
+
+    is_changed = 0;
+}
+
+void
+usage(void)
+{
+    fprintf(stderr, "usage: %s [-d] [-n TEXT]\n", PNAME);
+    fprintf(stderr, "  -d       daemonize\n");
+    fprintf(stderr, "  -n TEXT  send notification\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *s;
+    self_pid = getpid();
+    
+    if ((s = strrchr(argv[0], '/')) == NULL)
+        s = argv[0];
+    else
+        s++;
+
+    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;
+}