status

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

status.c (10260B)


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