status

statusbar program for dwm
git clone anongit@rnpnr.xyz:status.git
Log | Files | Refs | Feed | README | LICENSE

status.c (9693B)


      1 /* See LICENSE for license details. */
      2 #include <fcntl.h>
      3 #include <signal.h>
      4 #include <stdarg.h>
      5 #include <stddef.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 #include <sys/inotify.h>
     10 #include <sys/select.h>
     11 #include <time.h>
     12 #include <unistd.h>
     13 #include <X11/Xlib.h>
     14 
     15 #define ABS(a)         ((a) < 0 ? -(a) : (a))
     16 #define MIN(a, b)      ((a) < (b) ? (a) : (b))
     17 #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a))
     18 #define BLOCKLEN 128
     19 
     20 #define ISSPACE(c)     ((c) == ' ' || (c) == '\n' || (c) == '\t')
     21 
     22 #define TICK_RATE_SECONDS     1
     23 #define TICK_RATE_NANOSECONDS 0
     24 
     25 #define SLLPush(sll, new) do {     \
     26 	(new)->next = (sll)->next; \
     27 	(sll)->next = (new);       \
     28 } while(0)
     29 
     30 #define KB(n) ((n) << 10ULL)
     31 
     32 #ifndef asm
     33 #define asm __asm__
     34 #endif
     35 
     36 #ifndef typeof
     37 #define typeof __typeof__
     38 #endif
     39 
     40 #ifdef __ARM_ARCH_ISA_A64
     41 #define debugbreak() asm volatile ("brk 0xf000")
     42 #elif __x86_64__
     43 #define debugbreak() asm volatile ("int3; nop")
     44 #else
     45 #error Unsupported Platform!
     46 #endif
     47 
     48 #ifdef _DEBUG
     49 #define ASSERT(c) do { if (!(c)) debugbreak(); } while (0)
     50 #define DEBUG_EXPORT
     51 #else
     52 #define ASSERT(c) do { (void)(c); } while (0)
     53 #define DEBUG_EXPORT static
     54 #endif
     55 
     56 typedef float     f32;
     57 typedef double    f64;
     58 typedef uint8_t   u8;
     59 typedef uint32_t  b32;
     60 typedef uint32_t  u32;
     61 typedef int32_t   i32;
     62 typedef uint64_t  u64;
     63 typedef int64_t   i64;
     64 typedef ptrdiff_t size;
     65 typedef size_t    usize;
     66 
     67 typedef struct { u8 *beg, *end; } Arena;
     68 
     69 #define s8(s)  (s8){.len = sizeof(s) - 1,  .data = (u8 *)s}
     70 typedef struct { size len; u8 *data; } s8;
     71 
     72 typedef struct {
     73 	u8  *buffer;
     74 	i32  capacity;
     75 	i32  write_index;
     76 	b32  errors;
     77 } Stream;
     78 
     79 typedef struct {
     80 	void   *user_data;
     81 	void   *arg;
     82 	char   *fmt;
     83 	f32     timer;
     84 	u32     len;
     85 	char    data[BLOCKLEN];
     86 } Block;
     87 
     88 #define BLOCK_INIT_FN(name)   void name(Block *b, i32 block_index, Arena *a)
     89 #define BLOCK_UPDATE_FN(name) void name(Block *b)
     90 typedef BLOCK_UPDATE_FN(block_update_fn);
     91 
     92 typedef struct FileWatch {
     93 	struct FileWatch *next;
     94 	block_update_fn  *update_fn;
     95 	char             *path;
     96 	i32               block_index;
     97 	i32               wd;
     98 } FileWatch;
     99 
    100 /* TODO(rnp): replace this with arena usage */
    101 static char       buffer[KB(1)];
    102 static Stream     statusline;
    103 static void      *display;
    104 static i32        dirty_block_index = -1;
    105 static FileWatch  file_watches;
    106 
    107 static i32        dflag;
    108 
    109 i64 strtol(const char *, char **, i32);
    110 
    111 static void
    112 die(const char *errstr, ...)
    113 {
    114 	va_list ap;
    115 
    116 	va_start(ap, errstr);
    117 	vfprintf(stderr, errstr, ap);
    118 	va_end(ap);
    119 
    120 	_exit(1);
    121 }
    122 
    123 static f64
    124 get_time(void)
    125 {
    126 	struct timespec t;
    127 	clock_gettime(CLOCK_MONOTONIC, &t);
    128 	f64 result = t.tv_sec + ((f64)t.tv_nsec) * 1e-9;
    129 	return result;
    130 }
    131 
    132 static s8
    133 read_s8(char *path, s8 buffer)
    134 {
    135 	i32 fd     = open(path, O_RDONLY);
    136 	buffer.len = read(fd, buffer.data, buffer.len);
    137 	close(fd);
    138 	return buffer;
    139 }
    140 
    141 static i64
    142 read_i64(char *path)
    143 {
    144 	i64 result = 0;
    145 	char buffer[64];
    146 	s8 str = read_s8(path, (s8){.len = sizeof(buffer), .data = (u8 *)buffer});
    147 	if (str.len > 0) {
    148 		buffer[MIN(str.len, sizeof(buffer) - 1)] = 0;
    149 		result = strtol(buffer, 0, 10);
    150 	}
    151 	return result;
    152 }
    153 
    154 static void *
    155 mem_clear(void *_p, u8 c, usize size)
    156 {
    157 	u8 *p = _p;
    158 	for (usize i = 0; i < size; i++)
    159 		p[i] = c;
    160 	return p;
    161 }
    162 
    163 #define push_struct(a, t) alloc(a, t, 1)
    164 #define alloc(a, t, n)  (t *)alloc_(a, sizeof(t), _Alignof(t), n)
    165 static void *
    166 alloc_(Arena *a, size len, size align, size count)
    167 {
    168 	size padding   = -(uintptr_t)a->beg & (align - 1);
    169 	size available = a->end - a->beg - padding;
    170 	if (available <= 0 || available / len < count) {
    171 		ASSERT(0);
    172 	}
    173 
    174 	void *p  = a->beg + padding;
    175 	a->beg  += padding + count * len;
    176 	return mem_clear(p, 0, count * len);
    177 }
    178 
    179 static b32
    180 s8_equal(s8 a, s8 b)
    181 {
    182 	b32 result = a.len == b.len;
    183 	if (result) {
    184 		for (size i = 0; i < a.len; i++)
    185 			result &= a.data[i] == b.data[i];
    186 	}
    187 	return result;
    188 }
    189 
    190 static s8
    191 s8_trim_space(s8 a)
    192 {
    193 	while (a.len > 0 && ISSPACE(a.data[0]))         { a.len--; a.data++; }
    194 	while (a.len > 0 && ISSPACE(a.data[a.len - 1])) { a.len--; }
    195 	return a;
    196 }
    197 
    198 static Stream
    199 stream_alloc(Arena *a, size capacity)
    200 {
    201 	Stream result   = {0};
    202 	result.buffer   = alloc(a, u8, capacity);
    203 	result.capacity = capacity;
    204 	return result;
    205 }
    206 
    207 static char *
    208 stream_ensure_c_str(Stream *s)
    209 {
    210 	ASSERT(s->write_index < s->capacity);
    211 	s->buffer[s->write_index] = 0;
    212 	return (char *)s->buffer;
    213 }
    214 
    215 static void
    216 stream_push_s8(Stream *s, s8 str)
    217 {
    218 	s->errors |= s->capacity <= (s->write_index + str.len);
    219 	if (!s->errors) {
    220 		memcpy(s->buffer + s->write_index, str.data, str.len);
    221 		s->write_index += str.len;
    222 	}
    223 }
    224 
    225 static void
    226 add_file_watch(Arena *a, char *path, i32 block_index, block_update_fn *update_fn)
    227 {
    228 	i32 wd = inotify_add_watch(file_watches.wd, path, IN_CLOSE_WRITE|IN_MODIFY);
    229 	if (wd != -1) {
    230 		/* TODO(rnp): we will need to include inodes if we are expecting to watch
    231 		 * files that could be removed */
    232 		FileWatch *fw   = push_struct(a, FileWatch);
    233 		fw->wd          = wd;
    234 		fw->path        = path;
    235 		fw->block_index = block_index;
    236 		fw->update_fn   = update_fn;
    237 		SLLPush(&file_watches, fw);
    238 	}
    239 }
    240 
    241 #include "config.h"
    242 
    243 #define X(name, format, interval, argument) {.fmt = format, .arg = argument},
    244 static Block blocks[] = {
    245 	BLOCKS
    246 };
    247 #undef X
    248 
    249 #if TICK_RATE_NANOSECONDS > 999999999
    250 #error TICK_RATE_NANOSECONDS must not exceed 999 999 999
    251 #endif
    252 
    253 static void
    254 terminate(i32 signo)
    255 {
    256 	if (!dflag) {
    257 		XStoreName(display, DefaultRootWindow(display), 0);
    258 		XCloseDisplay(display);
    259 	}
    260 	_exit(0);
    261 }
    262 
    263 static void
    264 update_dirty_block_index(i32 new_index)
    265 {
    266 	if (dirty_block_index == -1 || new_index < dirty_block_index)
    267 		dirty_block_index = new_index;
    268 }
    269 
    270 static void
    271 dispatch_file_watch_events(Arena a)
    272 {
    273 	u8 *mem = alloc_(&a, 4096, 64, 1);
    274 	for (;;) {
    275 		size rlen = read(file_watches.wd, mem, 4096);
    276 		if (rlen <= 0)
    277 			break;
    278 
    279 		struct inotify_event *ie;
    280 		for (u8 *data = mem; data < mem + rlen; data += sizeof(*ie) + ie->len) {
    281 			ie = (void *)data;
    282 			for (FileWatch *fw = file_watches.next; fw; fw = fw->next) {
    283 				if (fw->wd != ie->wd)
    284 					continue;
    285 
    286 				b32 file_changed  = (ie->mask & IN_CLOSE_WRITE) != 0;
    287 				file_changed     |= (ie->mask & IN_MODIFY)      != 0;
    288 				/* TODO(rnp): it seems like this hits multiple times per update */
    289 				if (file_changed) {
    290 					fw->update_fn(blocks + fw->block_index);
    291 					update_dirty_block_index(fw->block_index);
    292 				}
    293 			}
    294 		}
    295 	}
    296 }
    297 
    298 static void
    299 update_status(void)
    300 {
    301 	statusline.write_index = 0;
    302 	i32 block_index;
    303 	for (block_index = 0; block_index < dirty_block_index; block_index++)
    304 		statusline.write_index += blocks[block_index].len;
    305 
    306 	for (; block_index < ARRAY_COUNT(blocks); block_index++) {
    307 		Block *b = blocks + block_index;
    308 		memcpy(statusline.buffer + statusline.write_index, b->data, b->len);
    309 		statusline.write_index += b->len;
    310 	}
    311 	statusline.buffer[statusline.write_index] = 0;
    312 
    313 	if (dflag) {
    314 		puts((char *)statusline.buffer);
    315 	} else {
    316 		XStoreName(display, DefaultRootWindow(display), (char *)statusline.buffer);
    317 		XSync(display, 0);
    318 	}
    319 }
    320 
    321 static b32
    322 timer_update(f32 *timer, f32 interval, f32 dt)
    323 {
    324 	b32 result = dt <= 0;
    325 	if (interval > 0) {
    326 		*timer -= dt;
    327 		while (*timer < 0) { *timer += interval; result = 1; }
    328 	}
    329 	return result;
    330 }
    331 
    332 static void
    333 update_blocks(f32 dt)
    334 {
    335 	i32 count = 0;
    336 	#define X(name, fmt, interval, args) \
    337 		if (timer_update(&blocks[count].timer, interval, dt)) { \
    338 			name ##_update(blocks + count);                 \
    339 			update_dirty_block_index(count);                \
    340 		}                                                       \
    341 		count++;
    342 	BLOCKS
    343 	#undef X
    344 }
    345 
    346 static void
    347 reload_all_blocks(i32 _unused)
    348 {
    349 	update_blocks(0);
    350 }
    351 
    352 static void
    353 status_init(Arena *a)
    354 {
    355 	if (!dflag && !(display = XOpenDisplay(0)))
    356 		die("XOpenDisplay: can't open display\n");
    357 
    358 	statusline = stream_alloc(a, 4096);
    359 
    360 	file_watches.wd = inotify_init1(O_NONBLOCK|O_CLOEXEC);
    361 
    362 	i32 count = 0;
    363 	#define X(name, fmt, interval, arg) name ##_init(blocks + count, count, a); count++;
    364 	BLOCKS
    365 	#undef X
    366 
    367 	struct sigaction sa;
    368 	sa.sa_flags = SA_RESTART;
    369 	sigemptyset(&sa.sa_mask);
    370 	sa.sa_handler = terminate;
    371 	sigaction(SIGINT,  &sa, 0);
    372 	sigaction(SIGTERM, &sa, 0);
    373 
    374 	sa.sa_handler = reload_all_blocks;
    375 	sigaction(SIGHUP, &sa, 0);
    376 
    377 	sa.sa_flags   = 0;
    378 	sa.sa_handler = SIG_IGN;
    379 	for (size i = SIGRTMIN; i <= SIGRTMAX; i++)
    380 		sigaction(i, &sa, 0);
    381 
    382 	update_status();
    383 }
    384 
    385 static void
    386 status_loop(Arena a)
    387 {
    388 	fd_set rfd;
    389 	f64 last_time = get_time();
    390 
    391 	for (;;) {
    392 		dirty_block_index = -1;
    393 		struct timespec t = {.tv_sec = TICK_RATE_SECONDS, .tv_nsec = TICK_RATE_NANOSECONDS};
    394 		FD_ZERO(&rfd);
    395 		FD_SET(file_watches.wd, &rfd);
    396 
    397 		pselect(file_watches.wd + 1, &rfd, 0, 0, &t, 0);
    398 		if (FD_ISSET(file_watches.wd, &rfd))
    399 			dispatch_file_watch_events(a);
    400 
    401 		f64 current_time = get_time();
    402 		f32 dt = current_time - last_time;
    403 		last_time = current_time;
    404 
    405 		update_blocks(dt);
    406 		if (dirty_block_index >= 0)
    407 			update_status();
    408 	}
    409 }
    410 
    411 static Arena
    412 get_arena(void)
    413 {
    414 	static u8 memory[KB(64)];
    415 	Arena a = {0};
    416 	a.beg = memory;
    417 	asm("" : "+r"(a.beg));
    418 	a.end = a.beg + sizeof(memory);
    419 	return a;
    420 }
    421 
    422 i32
    423 main(i32 argc, char *argv[])
    424 {
    425 	char *argv0 = *argv;
    426 	for (argv++; --argc && *argv && argv[0][0] == '-' && argv[0][1]; argv++) {
    427 		if (argv[0][1] == '-' && argv[0][2] == '\0') {
    428 			argv++;
    429 			argc--;
    430 			break;
    431 		}
    432 
    433 		for (i32 i = 1; argv[0][i]; i++) {
    434 			switch (argv[0][i]) {
    435 			case 'd': dflag = 1; break;
    436 			default:  die("usage: %s [-d]\n", argv0);
    437 			}
    438 		}
    439 	}
    440 
    441 	/* NOTE(rnp): fork ourselves to the background and run as a daemon */
    442 	if (!dflag) {
    443 		switch(fork()) {
    444 		case -1: die("failed to fork to background\n");
    445 		case  0: setsid(); break;
    446 		default: _exit(0);
    447 		}
    448 	}
    449 
    450 	Arena memory = get_arena();
    451 	status_init(&memory);
    452 	status_loop(memory);
    453 
    454 	return 0;
    455 }