#include "term.h" 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", }; // ----------------------------------------------------------------------- // 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; } static int tryextrabool(Term *t, char *name) { const char *nm; size_t max = unibi_count_ext_bool(t->info); for (size_t i = 0; i < max; i++) { nm = unibi_get_ext_bool_name(t->info, i); if (nm && !strcmp(nm, name)) { return (int)i; } } return -1; } /* formats escape strings and writes to output */ static void tfmt(Term *t, char *esc, int n, ...); // ----------------------------------------------------------------------- // exported pen methods // ----------------------------------------------------------------------- // 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 tinit(Term *t) { t->name = getenv("TERM"); t->info = unibi_from_term(t->name); if (!t->info) panicf("could not identify terminal"); 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); /* TODO */ t->input = nil; t->root = nil; t->pen = (Pen){0}; /* fill in 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"); /* acs characters */ t->acs.vline = tryinfostr(t, unibi_acs_vline); t->acs.hline = tryinfostr(t, unibi_acs_hline); t->acs.plus = tryinfostr(t, unibi_acs_plus); t->acs.ltee = tryinfostr(t, unibi_acs_ltee); t->acs.rtee = tryinfostr(t, unibi_acs_rtee); t->acs.ttee = tryinfostr(t, unibi_acs_ttee); t->acs.btee = tryinfostr(t, unibi_acs_btee); t->acs.ulcorner = tryinfostr(t, unibi_acs_ulcorner); t->acs.urcorner = tryinfostr(t, unibi_acs_urcorner); t->acs.llcorner = tryinfostr(t, unibi_acs_llcorner); t->acs.lrcorner = tryinfostr(t, unibi_acs_lrcorner); } void tfini(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); } 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); while (len > 0) { n = MIN(len, arrend(t->buf.b) - t->buf.c); memcpy(t->buf.c, s, n); t->buf.c += n; len -= n; tflush(t); } } 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: add a check for negative indices */ if (new.state & PenTrueClr) { 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 */ 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 */ 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); } void tdel(Term *t, int num) { char *c, buf[64]; if (num < 1) return; /* TODO: allow for not moving */ if (t->cap.bce) { tfmt(t, t->esc.ech, 1, num); tjump(t, 0, num); } else { c = buf; memset(c, ' ', arrlen(buf)); while(num > 64) { twrite(t, arrlen(buf), c); num -= arrlen(buf); } twrite(t, num, c); } } void tclear(Term *t) { tfmt(t, t->esc.ed2, 0); } // ----------------------------------------------------------------------- // testing entry int main() { Term t; tinit(&t); printf("%s: %s\n", unibi_short_name_str(unibi_set_a_foreground), t.esc.sgr_fg); printf("%s: %s\n", unibi_short_name_str(unibi_set_a_background), t.esc.sgr_bg); tfmt(&t, t.esc.ext.rgbf, 3, 10, 10, 100); tfmt(&t, t.esc.ext.rgbb, 3, 100, 0, 100); twrite(&t, 0, "hello world\n"); twrite(&t, 0, "hello world\n"); twrite(&t, 0, t.acs.hline); tfini(&t); }