ztatus

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

commit: 757467be124d0a86dc5d8709c565448b1afea434
parent: cd71558e90e3c5640954e704f50d2d10362ac2ac
author: Chris Noxz <chris@noxz.tech>
date:   Thu, 2 May 2019 18:33:58 +0200
use fifo instead of signals
MMakefile19+-
Mconfig.def.h62++--
Mztatus.c354+++++++++++---------
Aztatusc50+++
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