wmsl

Block-based window manager status line
git clone https://git.sinitax.com/sinitax/wmsl
Log | Files | Refs | README | LICENSE | sfeed.txt

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+++++
AMakefile | 15+++++++++++++++
AREADME | 37+++++++++++++++++++++++++++++++++++++
Ablocks.def.h | 12++++++++++++
Asigwmsl.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awmsl.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(); + } + } +}