#include #include #include #include #include #include #include #include "framebuffer.h" #include "uint8toa.h" size_t term_width = 0, term_height = 0; struct termios initial_terminal_state = {0}; Framebuffer fb_new(size_t width, size_t height) { Framebuffer fb; /* In case malloc() fails and we return early */ fb.width = 0; fb.height = 0; fb.fb = malloc(sizeof(Pixel *) * height); if (fb.fb == NULL) return fb; for (size_t i = 0; i < height; i++) { fb.fb[i] = malloc(sizeof(Pixel) * width); if (fb.fb[i] == NULL) { /* FIXME: Memory leak; should free all prev. malloced rows */ fb.fb = NULL; return fb; } } fb.print_buf = malloc((38 + sizeof(wchar_t)) * width * height); if (fb.print_buf == NULL) { /* FIXME: see above */ fb.fb = NULL; return fb; } fb.width = width; fb.height = height; return fb; } void fb_free(Framebuffer *fb) { if (fb->fb != NULL) { for (size_t i = 0; i < fb->height; i++) { free(fb->fb[i]); } free(fb->fb); } if (fb->print_buf != NULL) free(fb->print_buf); fb->width = 0; fb->height = 0; return; } void fb_fill(Framebuffer fb, Pixel pixel) { for (size_t row = 0; row < fb.height; row++) { for (size_t col = 0; col < fb.width; col++) { fb.fb[row][col] = pixel; } } } void fb_put(Framebuffer fb, size_t row, size_t col, Pixel pixel) { fb.fb[row][col] = pixel; } void fb_vert_line(Framebuffer fb, size_t x, size_t y1, size_t y2, Pixel pixel) { if (y2 < y1) { y2 ^= y1; y1 ^= y2; y2 ^= y1; }; for (size_t y = y1; y <= y2; y++) { fb.fb[y][x] = pixel; } } void fb_text(Framebuffer fb, long x, size_t y, char *s, Colour fg, Colour bg) { if (x >= (long)term_width || y >= term_height) return; /* OOB */ while (x < 0 && *s != '\0') { x++, s++; } Pixel p = {fg, bg, '\0'}; for(; *s != '\0' && x < (long)term_width; s++, x++) { p.ch = *s; fb.fb[y][x] = p; } } void fb_text_no_bg(Framebuffer fb, long x, size_t y, char *s, Colour fg) { if (x >= (long)term_width || y >= term_height) return; /* OOB */ while (x < 0 && *s != '\0') { x++, s++; } for(; *s != '\0' && x < (long)term_width; s++, x++) { fb.fb[y][x].ch = *s; fb.fb[y][x].fg = fg; } } void term_init(void) { tcgetattr(0, &initial_terminal_state); struct termios new = initial_terminal_state; new.c_lflag &= ~ICANON; /* Turn off canonical mode */ new.c_lflag &= ~ECHO; /* Turn off echoing */ new.c_cc[VMIN] = 0; /* Return read(STDIN_FILENO, ...) even if no input */ new.c_cc[VTIME] = 0; /* ^, make it return immediately, without waiting */ tcsetattr(0, TCSANOW, &new); write(STDOUT_FILENO, "\x1b[?1049h", 8); /* Alternative screen buffer */ write(STDOUT_FILENO, "\x1b[H", 3); /* Move cursor to top-left */ write(STDOUT_FILENO, "\x1b[?25l", 6); /* Hide the cursor */ term_update_size(); } void term_cleanup(void) { tcsetattr(0, TCSANOW, &initial_terminal_state); write(STDOUT_FILENO, "\x1b[?25h", 6); /* Show the cursor */ write(STDOUT_FILENO, "\x1b[?1049l", 8); /* Alternative screen buffer */ write(STDOUT_FILENO, "\x1b[0m", 4); /* Reset styling (colour) */ } void term_update_size(void) { #if defined(TIOCGWINSZ) struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { term_height = ws.ws_row; term_width = ws.ws_col; return; } #elif defined(TIOCGSIZE) struct ttysize ts; if (ioctl(STDOUT_FILENO, TIOCGSIZE, &ts) == 0) { term_height = ts.ts_row; term_width = ts.ts_col; return; } #else #error "Neither TIOCGWINSZ or TIOCGSIZE are defined, can't proceed" #endif } /* Converts pixel to ascii and puts it in buf, used in fb_print(). * This is faster than sprinf(). * TODO: maybe less memcpy()s will be better? */ static inline void pixel_to_ascii(char *buf, Pixel p) { /* Works even on W*ndows with their stupid UTF-16, I think (not tested lmao) */ memcpy(buf, "\x1b[38;2;XXX;XXX;XXXm\x1b[48;2;XXX;XXX;XXXmXXXX", 38 + sizeof(wchar_t)); memcpy(buf + 7, uint8toa[p.fg.r], 3); memcpy(buf + 11, uint8toa[p.fg.g], 3); memcpy(buf + 15, uint8toa[p.fg.b], 3); memcpy(buf + 26, uint8toa[p.bg.r], 3); memcpy(buf + 30, uint8toa[p.bg.g], 3); memcpy(buf + 34, uint8toa[p.bg.b], 3); memcpy(buf + 38, &p.ch, sizeof(wchar_t)); } void fb_print(Framebuffer fb) { /* TODO: move this to buffer, maybe? to avoid an extra write every 'frame' */ write(STDOUT_FILENO, "\x1b[H", 3); /* Move cursor to top-left */ /* 'cursor' of print_buf, used to build it */ char *cur = fb.print_buf; /* build the buffer */ for (size_t row = 0; row < fb.height; row++) { for (size_t col = 0; col < fb.width; col++) { pixel_to_ascii(cur, fb.fb[row][col]); cur += 38 + sizeof(wchar_t); } } /* flush the buffer */ write(STDOUT_FILENO, fb.print_buf, (38 + sizeof(wchar_t)) * fb.width * fb.height); } __attribute__((unused)) static inline void set_fg(Colour col) { fprintf(stdout, "\x1b[38;2;%u;%u;%um", col.r, col.g, col.g); fflush(stdout); } __attribute__((unused)) static inline void set_bg(Colour col) { fprintf(stdout, "\x1b[48;2;%u;%u;%um", col.r, col.g, col.g); fflush(stdout); }