From ce05175372a9ddca1a225db0765ace1127a39293 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 12 Nov 2021 09:22:01 -0800 Subject: chore: simplified organizational structure --- src/libterm/term.c | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 src/libterm/term.c (limited to 'src/libterm/term.c') diff --git a/src/libterm/term.c b/src/libterm/term.c new file mode 100644 index 0000000..11591fc --- /dev/null +++ b/src/libterm/term.c @@ -0,0 +1,489 @@ +#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); +} -- cgit v1.2.1