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}