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 }