#include "term.h" #include #include struct ExtraInfo { char *enteralt; char *exitalt; char *entermouse; char *exitmouse; }; static struct ExtraInfo vt200 = { .enteralt = "\e[?1049h", .exitalt = "\e[?1049l", .entermouse = "\e[?1049h\e[?1006l", .exitmouse = "\e[?1002l\e[?1006l", }; static Term *sigwinchhead; // ----------------------------------------------------------------------- // database lookup static char* tryinfostr(Term *t, enum unibi_string s) { char *val = (char*)unibi_get_str(t->info, s); /* TODO: provide fallbacks */ return val; } static char* guessinfostr(Term *t, enum unibi_string s, char *guess) { char *val = (char*)unibi_get_str(t->info, s); if (!val) return guess; return val; } static char* getinfostr(Term *t, enum unibi_string s) { char *val = tryinfostr(t, s); if (!val) panicf("required term info string '%s' missing", unibi_name_str(s)); return val; } static char * tryextrastr(Term *t, char *name) { const char *nm; size_t max = unibi_count_ext_str(t->info); for (size_t i = 0; i < max; i++) { nm = unibi_get_ext_str_name(t->info, i); if (nm && !strcmp(nm, name)) { return (char *)nm; } } return nil; } static char * guessextrastr(Term *t, char *name, char *guess) { char *s; if ((s = tryextrastr(t, name))) return s; return guess; } /* formats escape strings and writes to output */ static void tfmt(Term *t, char *esc, int n, ...); static void tclear(Term *t); // ----------------------------------------------------------------------- // exported term methods static char * ttmpbuf(Term *t, int len) { if (t->tmp.len >= len) return t->tmp.b; /* TODO: error handling */ return (t->tmp.b = realloc(t->tmp.b, len)); } void twrite(Term *t, long len, char *s); void tlistensigwinch(Term *t); Term* tmake(void) { Term *t; t = calloc(1, sizeof(*t)); /* meta data */ t->name = getenv("TERM"); t->info = unibi_from_term(t->name); if (!t->info) panicf("could not identify terminal"); t->fd = 1; // stdout tlistensigwinch(t); t->mode.mouse = 0; t->mode.cursorvis = 1; t->mode.altscreen = 0; t->cap.colors = unibi_get_num(t->info, unibi_max_colors); t->cap.bce = unibi_get_bool(t->info, unibi_back_color_erase); /* initialize root window (get current size)*/ struct winsize ws = { 0 }; if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1) goto bad; t->root = wmake(nil, 0, 0, ws.ws_col, ws.ws_row, 0); t->root->curvis = 1; t->root->blink = 0; t->pen = (Pen){ .state = PenNormal, .col = {.fg = -1, .bg = -1}, }; /* fill in output buffers */ t->buf.c = t->buf.b; t->tmp.b = nil; t->tmp.len = 0; /* get all term info format strings */ t->esc.cup = getinfostr(t, unibi_cursor_address); t->esc.vpa = tryinfostr(t, unibi_row_address); t->esc.hpa = tryinfostr(t, unibi_column_address); t->esc.cuu = getinfostr(t, unibi_parm_up_cursor); t->esc.cuu1 = tryinfostr(t, unibi_cursor_up); t->esc.cud = getinfostr(t, unibi_parm_down_cursor); t->esc.cud1 = tryinfostr(t, unibi_cursor_down); t->esc.cuf = getinfostr(t, unibi_parm_right_cursor); t->esc.cuf1 = tryinfostr(t, unibi_cursor_right); t->esc.cub = getinfostr(t, unibi_parm_left_cursor); t->esc.cub1 = tryinfostr(t, unibi_cursor_left); t->esc.ich = getinfostr(t, unibi_parm_ich); t->esc.ich1 = tryinfostr(t, unibi_insert_character); t->esc.dch = getinfostr(t, unibi_parm_dch); t->esc.dch1 = tryinfostr(t, unibi_delete_character); t->esc.il = getinfostr(t, unibi_parm_insert_line); t->esc.il1 = tryinfostr(t, unibi_insert_line); t->esc.dl = getinfostr(t, unibi_parm_delete_line); t->esc.dl1 = tryinfostr(t, unibi_delete_line); t->esc.ech = getinfostr(t, unibi_erase_chars); t->esc.ed2 = getinfostr(t, unibi_clear_screen); t->esc.stbm = getinfostr(t, unibi_change_scroll_region); t->esc.sgr = getinfostr(t, unibi_set_attributes); t->esc.sgr0 = getinfostr(t, unibi_exit_attribute_mode); t->esc.sgr_i0 = tryinfostr(t, unibi_exit_italics_mode); t->esc.sgr_i1 = tryinfostr(t, unibi_enter_italics_mode); t->esc.sgr_fg = getinfostr(t, unibi_set_a_foreground); t->esc.sgr_bg = getinfostr(t, unibi_set_a_background); t->esc.sm_csr = getinfostr(t, unibi_cursor_normal); t->esc.rm_csr = getinfostr(t, unibi_cursor_invisible); /* extensions to terminfo */ t->esc.ext.rgbf = guessextrastr(t, "setrgbf", "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); t->esc.ext.rgbb = guessextrastr(t, "setrgbb", "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); return t; bad: panicf("failed to initialize terminal instance"); free(t); return nil; } void tfree(Term *t) { if (t->mode.mouse) twrite(t, 0, vt200.exitmouse); if (!t->mode.cursorvis) tfmt(t, t->esc.rm_csr, 0); if (t->mode.altscreen) twrite(t, 0, vt200.exitalt); tfmt(t, t->esc.sgr0, 0); tclear(t); free(t); } /* handle resize events */ void tresize(Term *t) { if (t->fd == -1) return; struct winsize ws = { 0 }; if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1) return; printf("[%d,%d]\n", ws.ws_col, ws.ws_row); if (t->root->w != ws.ws_col || t->root->h != ws.ws_row) wresize(t->root, ws.ws_col, ws.ws_row); } static void sigwinch(int num) { Term *it; for (it = sigwinchhead; it; it = it->link) tresize(it); } void tlistensigwinch(Term *t) { sigset_t new, old; Term *it; sigemptyset(&new); sigaddset(&new, SIGWINCH); sigprocmask(SIG_BLOCK, &new, &old); if (!sigwinchhead) { sigaction(SIGWINCH, &(struct sigaction){ .sa_handler = sigwinch }, nil); sigwinchhead = t; } else { it = sigwinchhead; while (it->link) it = it->link; it->link = t; } sigprocmask(SIG_SETMASK, &old, nil); } void tflush(Term *t) { if (t->fd != -1) write(t->fd, t->buf.b, t->buf.c - t->buf.b); t->buf.c = t->buf.b; } void twrite(Term *t, long len, char *s) { int n; if (!len) len = strlen(s); loop: n = MIN(len, arrend(t->buf.b) - t->buf.c); memcpy(t->buf.c, s, n); t->buf.c += n; len -= n; if (len) { tflush(t); goto loop; } } void tsetpen(Term *t, Pen new) { int c; ushort ic, in; Pen cur = t->pen; if (!memcmp(&new, &cur, sizeof(new))) return; /* attributes */ tfmt(t, t->esc.sgr, 9, 0, /* standout */ new.state & PenUnderline, new.state & PenReverse, new.state & PenBlink, new.state & PenDim, new.state & PenBold, new.state & PenInvis, 0, /* protect */ 0); /* alt */ ic = cur.state & PenItalic; in = new.state & PenItalic; if (ic & ~in) tfmt(t, t->esc.sgr_i0, 0); else if (~ic & in) tfmt(t, t->esc.sgr_i1, 0); /* fg/bg color */ /* TODO: add a check for if the terminal supports true color */ /* TODO: deal w/ negative indices properly */ if (new.state & PenRGB) { tfmt(t, t->esc.ext.rgbf, 3, new.rgb.fg.r, new.rgb.fg.g, new.rgb.fg.b); tfmt(t, t->esc.ext.rgbb, 3, new.rgb.bg.r, new.rgb.bg.g, new.rgb.bg.b); } else { tfmt(t, t->esc.sgr_fg, 1, new.col.fg); tfmt(t, t->esc.sgr_bg, 1, new.col.bg); } t->pen = new; } static void tfmt(Term *t, char *esc, int n, ...) { int i; long len; va_list args; unibi_var_t param[9]; char buf[64], *c = buf; if (!esc) panicf("no terminfo escape string given"); va_start(args, n); for (i = 0; i < arrlen(param) && i < n; i++) { param[i] = unibi_var_from_num(va_arg(args, int)); } va_end(args); len = unibi_run(esc, param, c, sizeof(buf)); if (len >= arrlen(buf)) { c = ttmpbuf(t, len); unibi_run(esc, param, c, len); } twrite(t, len, c); } /* absolute move */ static int tgoto(Term *t, int row, int col) { if (row != -1 && col != -1) tfmt(t, t->esc.cup, 2, row, col); else if (row != -1) { if (!t->esc.vpa) return 0; tfmt(t, t->esc.vpa, 1, row); } else if (col != -1) { if (col == 0) { twrite(t, 1, "\r"); return 1; } if (t->esc.hpa) tfmt(t, t->esc.hpa, 1, col); else if (t->esc.cuf) { twrite(t, 1, "\r"); tfmt(t, t->esc.cuf, 1, col); } else return 0; } else return 0; /* unreachable */ return 1; } /* relative move */ static void tjump(Term *t, int down, int right) { if (down == 1 && t->esc.cud1) tfmt(t, t->esc.cud1, 0); else if (down == -1 && t->esc.cuu1) tfmt(t, t->esc.cuu1, 0); else if (down > 0) tfmt(t, t->esc.cud, 1, down); else if (down < 0) tfmt(t, t->esc.cuu, 1, -down); if (right == 1 && t->esc.cuf1) tfmt(t, t->esc.cuf1, 0); else if (right == -1 && t->esc.cub1) tfmt (t, t->esc.cub1, 0); else if (right > 0) tfmt(t, t->esc.cuf, 1, right); else if( right < 0) tfmt(t, t->esc.cub, 1, -right); } static void tclear(Term *t) { tfmt(t, t->esc.ed2, 0); } void tblit(Term *t, Window *win) { int r, c, n, j; Row *row; char u[UTFmax+1] = {0}; j = 0; tgoto(t, win->top, win->left); for (r = 0; r < win->h; r++) { row = win->row + r; if (!row->dirty) { j++; continue; } if (j) { tjump(t, j, 0); j = 0; } for (c = 0; c < win->w; c++) { tsetpen(t, row->cells[c].pen); n = utf8·runetobyte(u, &row->cells[c].txt); twrite(t, n, u); } row->dirty = 0; } tflush(t); } // ----------------------------------------------------------------------- // testing int main() { int i; Term *t; Window *win; t = tmake(); win = t->root; tclear(t); win->pen = (Pen){ .state = PenNormal, .col = {.fg=-1, .bg=-1}, }; for (i = 0; i < 2000; i++) wputrune(win, 'a'); tblit(t, win); win->cur.row = 10; win->cur.col = 0; win->pen = (Pen){ .state=PenNormal|PenRGB, .rgb={.fg={200, 100, 100}, .bg={0, 0, 0} }, }; for (i = 0; i < 500; i++) wputrune(win, 'b'); tblit(t, win); sleep(5); wscroll(win, 10); tblit(t, win); sleep(5); tfree(t); }