wmsl

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

wmsl.c (6737B)


      1#include <X11/Xlib.h>
      2
      3#include <bits/time.h>
      4#include <sys/poll.h>
      5#include <sys/wait.h>
      6#include <unistd.h>
      7#include <err.h>
      8#include <errno.h>
      9#include <signal.h>
     10#include <time.h>
     11#include <math.h>
     12#include <string.h>
     13#include <stdarg.h>
     14#include <stdbool.h>
     15#include <stdlib.h>
     16#include <stdio.h>
     17#include <stdint.h>
     18
     19#define OUTPUTMAX  2048
     20
     21struct block {
     22	struct block *next;
     23	const char *command;
     24	bool ready;
     25	int id, sleep;
     26	time_t sleep_left;
     27	char output[OUTPUTMAX];
     28};
     29
     30static const char *usage = "Usage: wmsl [-C FILE] [-h] [-v]\n";
     31static const char *pid_file = "/tmp/wmsl.pid";
     32static pid_t pid;
     33
     34static char status_text[OUTPUTMAX];
     35static size_t status_len;
     36
     37static Display *dpy;
     38static Window root;
     39
     40static char config[1024];
     41
     42static struct block *blocks;
     43
     44static char *delim;
     45static char *prefix;
     46static char *suffix;
     47
     48static void
     49update_status(const char *str)
     50{
     51	XStoreName(dpy, root, str);
     52	XFlush(dpy);
     53}
     54
     55static void
     56sigexit(int sig)
     57{
     58	update_status("");
     59	exit(128 + sig);
     60}
     61
     62static time_t
     63now_ms(void)
     64{
     65	struct timespec now;
     66
     67	if (clock_gettime(CLOCK_REALTIME, &now))
     68		err(1, "clock_gettime REALTIME");
     69
     70	return now.tv_sec * 1000L + now.tv_nsec / 1000000L;
     71}
     72
     73static void
     74sighandler(int sig, siginfo_t *info, void *context)
     75{
     76	struct block *block;
     77	int block_id;
     78
     79	block_id = info->si_value.sival_int;
     80	if (!block_id || info->si_pid == pid) return;
     81
     82	for (block = blocks; block; block = block->next) {
     83		if (block->id == block_id) {
     84			block->ready = true;
     85			break;
     86		}
     87	}
     88}
     89
     90static void
     91concat_status(const char *text)
     92{
     93	size_t len = strlen(text);
     94	if (status_len + len + 1 > OUTPUTMAX)
     95		len = OUTPUTMAX - status_len - 1;
     96	memcpy(&status_text[status_len], text, len);
     97	status_len += len;
     98	if (status_len == OUTPUTMAX)
     99		strcpy(&status_text[status_len - 4], "...");
    100	status_text[status_len] = '\0';
    101}
    102
    103static int
    104run_block(const char *cmd, pid_t *pid)
    105{
    106	int out[2];
    107
    108	if (pipe(out) == -1)
    109		err(1, "pipe");
    110
    111	*pid = fork();
    112	if (*pid < 0) err(1, "fork");
    113
    114	if (*pid) {
    115		close(out[1]);
    116	} else {
    117		close(0);
    118		dup2(out[1], 1);
    119		close(out[0]);
    120		close(out[1]);
    121		execl("/bin/sh", "sh", "-c", cmd, NULL);
    122		abort();
    123	}
    124
    125	return out[0];
    126}
    127
    128static void
    129read_block(int fd, char *buf, size_t size)
    130{
    131	time_t start_ms, end_ms;
    132	struct pollfd pollfd;
    133	ssize_t nread;
    134	char *tok;
    135
    136	pollfd.fd = fd;
    137	pollfd.events = POLLIN;
    138
    139	start_ms = now_ms();
    140	end_ms = start_ms + 1000; /* 1 second timeout */
    141
    142	*buf = '\0';
    143	while (end_ms > start_ms) {
    144		if (poll(&pollfd, 1, (int) (end_ms - start_ms)) <= 0)
    145			break;
    146		start_ms = now_ms();
    147
    148		if (!(pollfd.revents & POLLIN))
    149			continue;
    150
    151		nread = read(fd, buf, size);
    152		if (nread <= 0) break;
    153
    154		tok = memchr(buf, '\n', (size_t) nread);
    155		if (tok) *tok = '\0';
    156
    157		buf += nread;
    158		size -= (size_t) nread;
    159
    160		if (tok) break;
    161	}
    162}
    163
    164static void
    165update_blocks(void)
    166{
    167	struct block *block;
    168	char buf[OUTPUTMAX];
    169	pid_t child;
    170	bool update;
    171	int fd;
    172
    173	status_text[0] = '\0';
    174	status_len = 0;
    175
    176	concat_status(prefix);
    177
    178	update = false;
    179	for (block = blocks; block; block = block->next) {
    180		if (block->ready || block->sleep && block->sleep_left <= 50) {
    181			block->sleep_left = block->sleep * 1000;
    182			block->ready = false;
    183
    184			fd = run_block(block->command, &child);
    185
    186			read_block(fd, buf, sizeof(buf)-1);
    187			if (strcmp(buf, block->output)) {
    188				strncpy(block->output, buf, OUTPUTMAX);
    189				update = true;
    190			}
    191
    192			close(fd);
    193			kill(child, SIGKILL);
    194			waitpid(child, NULL, 0);
    195		}
    196
    197		if (*block->output) {
    198			if (status_len > 0)
    199				concat_status(delim);
    200			concat_status(block->output);
    201		}
    202	}
    203	if (!update) return;
    204
    205	concat_status(suffix);
    206
    207	update_status(status_text);
    208}
    209
    210static void
    211sleep_next(void)
    212{
    213	struct block *block;
    214	time_t min_sleep;
    215	time_t start_ms;
    216	time_t stop_ms;
    217
    218	min_sleep = 0;
    219	for (block = blocks; block; block = block->next) {
    220		if (block->ready || block->sleep && block->sleep_left <= 0)
    221			return;
    222		if (!min_sleep || block->sleep && block->sleep_left < min_sleep)
    223			min_sleep = block->sleep_left;
    224	}
    225
    226	start_ms = now_ms();
    227	poll(NULL, 0, (int) min_sleep);
    228	stop_ms = now_ms();
    229
    230	for (block = blocks; block; block = block->next) {
    231		if (block->sleep) {
    232			block->sleep_left -= (stop_ms - start_ms);
    233		}
    234	}
    235}
    236
    237static void
    238read_config(void)
    239{
    240	struct block **block;
    241	char line[256], *tok;
    242	FILE *file;
    243
    244	file = fopen(config, "r");
    245	if (!file) err(1, "fopen '%s'", config);
    246
    247	block = &blocks;
    248	while (fgets(line, 256, file)) {
    249		if (*line == '#' || strspn(line, " \n\v\t") == strlen(line))
    250			continue;
    251		if ((tok = strchr(line, '\n')))
    252			*tok = '\0';
    253		if (!strncmp(line, "delim=", 6)) {
    254			delim = strdup(line + 6);
    255		} else if (!strncmp(line, "prefix=", 7)) {
    256			prefix = strdup(line + 7);
    257		} else if (!strncmp(line, "suffix=", 7)) {
    258			suffix = strdup(line + 7);
    259		} else if (!strncmp(line, "block=", 6)) {
    260			if (block != &blocks || *block)
    261				block = &(*block)->next;
    262			*block = malloc(sizeof(struct block));
    263			(*block)->command = strdup(line + 6);
    264			(*block)->id = 0;
    265			(*block)->sleep_left = 0;
    266			(*block)->sleep = 0;
    267			(*block)->ready = true;
    268			(*block)->next = NULL;
    269		} else if (!strncmp(line, "block-id=", 9)) {
    270			if (!*block) errx(1, "missing block=");
    271			(*block)->id = atoi(line + 9);
    272		} else if (!strncmp(line, "block-sleep=", 12)) {
    273			if (!*block) errx(1, "missing block=");
    274			(*block)->sleep = atoi(line + 12);
    275		} else {
    276			errx(1, "invalid config");
    277		}
    278	}
    279
    280	if (!suffix) suffix = " ";
    281	if (!prefix) prefix = "";
    282	if (!delim) delim = " | ";
    283
    284	fclose(file);
    285}
    286
    287int
    288main(int argc, const char **argv)
    289{
    290	const char **arg;
    291	struct sigaction sa;
    292	const char *home;
    293	int screen;
    294	FILE *file;
    295
    296	if (argc <= 0) return 1; 
    297
    298	for (arg = argv + 1; *arg; arg++) {
    299		if ((*arg)[0] != '-')
    300			errx(1, "invalid arg: %s", *arg);
    301		switch ((*arg)[1]) {
    302		case 'C':
    303			strncpy(config, *++arg, sizeof(config));
    304			break;
    305		case 'h':
    306			fputs(usage, stdout);
    307			return 0;
    308		default:
    309			fputs(usage, stderr);
    310			return 1;
    311		}
    312	}
    313
    314	if (!*config) {
    315		home = getenv("HOME");
    316		if (!home) errx(1, "not config");
    317		snprintf(config, sizeof(config),
    318			"%s/.config/wmsl/wmslrc", home);
    319	}
    320
    321	read_config();
    322
    323	/* get root window */
    324	dpy = XOpenDisplay(NULL);
    325	if (!dpy) errx(1, "XOpenDisplay root window");
    326	screen = DefaultScreen(dpy);
    327	root = RootWindow(dpy, screen);
    328
    329	/* save pid */
    330	pid = getpid();
    331	file = fopen(pid_file, "w+");
    332	if (!file) err(1, "fopen '%s'", pid_file);
    333	fprintf(file, "%i", pid);
    334	fclose(file);
    335
    336	/* setup signal handlers & cleanup */
    337	sigemptyset(&sa.sa_mask);
    338	sa.sa_handler = NULL;
    339	sa.sa_sigaction = sighandler;
    340	sa.sa_flags = SA_RESTART | SA_SIGINFO;
    341	if (sigaction(SIGUSR1, &sa, NULL))
    342		err(1, "sigaction");
    343	signal(SIGINT, sigexit);
    344	signal(SIGTERM, sigexit);
    345
    346	while (1) {
    347		sleep_next();
    348		update_blocks();
    349	}
    350}