commit 0ddd6639021b2cc5d8ecce1bf36e0e2b0e3e328e
Author: Louis Burda <quent.burda@gmail.com>
Date: Sat, 12 Sep 2020 21:55:59 +0200
Add initial version with basic functionality
Diffstat:
A | .gitignore | | | 5 | +++++ |
A | Makefile | | | 15 | +++++++++++++++ |
A | README | | | 37 | +++++++++++++++++++++++++++++++++++++ |
A | blocks.def.h | | | 12 | ++++++++++++ |
A | sigwmsl.c | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | wmsl.c | | | 257 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 384 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,5 @@
+sigwmsl
+wmsl
+blocks.h
+compile_commands.json
+.clangd
diff --git a/Makefile b/Makefile
@@ -0,0 +1,15 @@
+CFLAGS = -I.
+LDLIBS = -lX11 -lm
+
+all: wmsl sigwmsl
+
+install: all
+ sudo cp sigwmsl wmsl /usr/bin
+
+blocks.h: blocks.def.h
+ cp blocks.def.h blocks.h
+
+wmsl: wmsl.c blocks.h
+ $(CC) -o $@ $< $(CFLAGS) $(LDLIBS)
+
+sigwmsl: sigwmsl.c
diff --git a/README b/README
@@ -0,0 +1,37 @@
+wmsl - window manager status line
+=================================
+
+wmsl is a simple and extensible status line for dwm
+
+
+Features
+--------
+- status 'blocks' are updated by timer or unique signal
+- scriptable, blocks can be added dynamically without recompilation
+- simple and extensible <200 lines of C
+
+
+Methodology
+-----------
+To update blocks independent of the timer, a signal handler is bound to SIGALRM,
+the block to update is specified via id in the SIGVAL. This way, both update
+signals and timers (through alarm()) use the same handler. This results in a
+very simple and neat implementation, where the signals specified for each block
+are also independent of the range of realtime signal for the system. The
+downside is, that the signal value cannot be used to pass information to a
+script, however, this can easily be done via a tmp file with more flexibility.
+
+Instead of waiting the GCD of all timer values (or worse, plainly 1 sec), wmsl
+waits as long as possible, until the next block must be run. This way, given two
+scripts with update intervals 3 and 10, which are coprime, instead of waiting 10
+times in 10 seconds, we wait 4 times e.g. 3s, 3s, 3s, 1s.
+
+The status bar is only updated, if the status has changed. However, a new status
+is compared with the previously set status BY WMSL, not the current, potentially
+changed status.
+
+
+Requirements
+------------
+Xlib header files for setting root window property WM_NAME
+
diff --git a/blocks.def.h b/blocks.def.h
@@ -0,0 +1,12 @@
+#define SCRIPT_DIR(x) "/opt/wmsl-scripts" x
+
+/* use READY or IDLE to set if script should be run on startup */
+
+struct block blocks[] = {
+ /* command block-id interval flags */
+ { SCRIPT_DIR("calendar.sh"), 0, 5, READY }
+};
+
+const char delim[] = " | ";
+const char prefix[] = "";
+const char suffix[] = " ";
diff --git a/sigwmsl.c b/sigwmsl.c
@@ -0,0 +1,58 @@
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+void die(int rc, const char *errstr, ...);
+
+const char* pid_file = "/tmp/.wmsl-pid";
+
+void
+die(int rc, const char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vprintf(errstr, ap);
+ va_end(ap);
+ exit(rc);
+}
+
+int
+main(int argc, const char **argv)
+{
+ FILE *f;
+ char buffer[64];
+ pid_t pid;
+ sigval_t sigval;
+ int block_id;
+
+ if (argc == 1)
+ die(EXIT_SUCCESS, "USAGE: sigwmsl <block-id>\n");
+
+ block_id = atoi(argv[1]);
+ if (!block_id)
+ die(EXIT_FAILURE, "sigwmsl: invalid block id\n");
+
+ f = fopen(pid_file, "r");
+ if (!f)
+ die(EXIT_FAILURE, "sigwmsl: Failed to open pid file %s\n", pid_file);
+
+ if (!fgets(buffer, 64, f))
+ die(EXIT_FAILURE, "sigwmsl: PID file is empty\n");
+
+ pid = atoi(buffer);
+
+ sigval.sival_int = block_id;
+ if (sigqueue(pid, SIGALRM, sigval) == -1) {
+ switch (errno) {
+ case EINVAL:
+ die(EXIT_FAILURE, "sigwmsl: invalid signal value\n");
+ case ESRCH:
+ die(EXIT_FAILURE, "sigwmsl: wmsl is not running\n");
+ }
+ }
+
+ fclose(f);
+}
diff --git a/wmsl.c b/wmsl.c
@@ -0,0 +1,257 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <signal.h>
+
+#include <X11/Xlib.h>
+
+#define ARRSIZE(x) (sizeof(x)/sizeof(x[0]))
+#define OUTPUTMAX 256
+#define STATUSMAX 1024
+
+enum block_state {
+ IDLE,
+ READY
+};
+
+struct block {
+ const char *command;
+ unsigned int id, sleep_max;
+ int flags;
+ float sleep_left;
+ char output[OUTPUTMAX];
+};
+
+static void die(int rc, const char *errstr, ...);
+static void signal_handler(int sig, siginfo_t *info, void *context);
+static void concat_status(const char *text, size_t len);
+static void update_blocks();
+static void sleep_till_next();
+static float get_elapsed();
+
+static const char* pid_file = "/tmp/.wmsl-pid";
+static char status_text[STATUSMAX];
+static float last_sleep;
+static Display* dpy;
+static Window root;
+static size_t status_len;
+static int verbose;
+static pid_t pid;
+
+#include "blocks.h"
+
+void
+die(int rc, const char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(rc);
+}
+
+void
+signal_handler(int sig, siginfo_t *info, void *context)
+{
+ int i, block_id;
+
+ block_id = info->si_value.sival_int;
+ if (!block_id || info->si_pid == pid) return;
+
+ if (verbose)
+ printf("received signal for block with id %i\n", block_id);
+
+ for (i = 0; i < ARRSIZE(blocks); i++) {
+ if (blocks[i].id == block_id) {
+ blocks[i].flags |= READY;
+ break;
+ }
+ }
+}
+
+void
+concat_status(const char *text, size_t len)
+{
+ if (status_len + len + 1 > STATUSMAX)
+ die(EXIT_FAILURE, "wmsl: status line too large (>%i)\n",
+ STATUSMAX);
+ memcpy(&status_text[status_len], text, len);
+ status_len += len;
+ status_text[status_len] = '\0';
+}
+
+void
+update_blocks()
+{
+ FILE *cmd;
+ int i, output_len, update;
+ char tmp_output[OUTPUTMAX];
+
+ if (verbose)
+ printf("checking block output..\n");
+
+ memcpy(status_text, prefix, ARRSIZE(prefix) - 1);
+
+ /* construct status string */
+ concat_status(prefix, ARRSIZE(prefix) - 1);
+ for (status_len = update = i = 0; i < ARRSIZE(blocks); i++) {
+ if ((blocks[i].flags & READY) ||
+ blocks[i].sleep_max && blocks[i].sleep_left <= 0.5) {
+ blocks[i].sleep_left = blocks[i].sleep_max;
+ blocks[i].flags = IDLE;
+
+ /* get command output */
+ cmd = popen(blocks[i].command, "r");
+ if (!cmd)
+ continue;
+
+ if (!fgets(tmp_output, OUTPUTMAX, cmd)) {
+ output_len = 0;
+ } else {
+ output_len = strlen(tmp_output);
+ if (tmp_output[output_len - 1] == '\n')
+ output_len -= 1;
+ }
+
+ pclose(cmd);
+
+ if (output_len == strlen(blocks[i].output)
+ && !strncmp(tmp_output, blocks[i].output, output_len)) {
+ continue;
+ } else {
+ memcpy(blocks[i].output, tmp_output, output_len);
+ if (verbose)
+ printf("new output from cmd: %s\n", blocks[i].command);
+ update = 1;
+ }
+ } else {
+ output_len = strlen(blocks[i].output);
+ }
+
+ if (output_len) {
+ if (status_len > 0)
+ concat_status(delim, ARRSIZE(delim) - 1);
+ concat_status(blocks[i].output, output_len);
+ }
+ }
+ if (!update) return;
+ concat_status(suffix, ARRSIZE(suffix) - 1);
+
+ status_text[status_len] = '\0';
+
+ XStoreName(dpy, root, status_text);
+ XFlush(dpy);
+}
+
+void
+sleep_till_next()
+{
+ int i, minsleep;
+
+ last_sleep = 0; /* reset last_sleep in case we return early */
+ for (i = minsleep = 0; i < ARRSIZE(blocks); i++) {
+ blocks[i].sleep_left -= last_sleep;
+ if ((blocks[i].flags & READY) ||
+ blocks[i].sleep_max && blocks[i].sleep_left <= 0)
+ return;
+ if (!minsleep || blocks[i].sleep_left < minsleep && blocks[i].sleep_max)
+ minsleep = ceil(blocks[i].sleep_left);
+ }
+
+ if (minsleep < 1)
+ minsleep = 1;
+
+ if (verbose)
+ printf("sleeping for %i seconds\n", minsleep);
+
+ alarm(minsleep);
+ pause();
+
+ last_sleep = get_elapsed();
+ if (verbose)
+ printf("slept for %f seconds\n", last_sleep);
+}
+
+float
+get_elapsed()
+{
+ static struct timespec last = { .tv_nsec = -1 };
+ struct timespec cur;
+ float diff;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &cur) == -1)
+ die(EXIT_FAILURE, "wmsl: failed to get time via clock()\n");
+
+ if (last.tv_nsec == -1)
+ diff = 0;
+ else
+ diff = cur.tv_sec - last.tv_sec
+ + (cur.tv_nsec - last.tv_nsec) / 1000000000.f;
+
+ last = cur;
+ return diff;
+}
+
+int
+main(int argc, const char **argv)
+{
+ struct sigaction sa;
+ int i, screen, sleep;
+ FILE* f;
+
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '-')
+ switch (argv[i][1]) {
+ case 'h':
+ die(EXIT_SUCCESS, "USAGE: wmsl [-h] [-v]\n");
+ case 'v':
+ verbose = 1;
+ break;
+ }
+ }
+
+ /* get root window */
+ dpy = XOpenDisplay(NULL);
+ if (!dpy)
+ die(EXIT_FAILURE, "wmsl: Failed to open display\n");
+ screen = DefaultScreen(dpy);
+ root = RootWindow(dpy, screen);
+
+ /* setup signal handler */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = NULL;
+ sa.sa_sigaction = signal_handler;
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ die(EXIT_FAILURE, "wmsl: Failed to setup signal handler (SIGALRM)\n");
+
+ /* save pid */
+ pid = getpid();
+ f = fopen(pid_file, "w+");
+ if (!f)
+ die(EXIT_FAILURE, "wmsl: Failed to open pid file %s\n", pid_file);
+ fprintf(f, "%i", pid);
+ fclose(f);
+
+ /* check if any blocks require timers */
+ for (i = 0; i < ARRSIZE(blocks); i++)
+ if (blocks[i].sleep_max != 0) break;
+
+ if (i != ARRSIZE(blocks)) {
+ get_elapsed();
+ while (1) {
+ sleep_till_next();
+ update_blocks();
+ }
+ } else {
+ while (1) {
+ pause();
+ update_blocks();
+ }
+ }
+}