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 }