From 6f84b07d939be6ef5e50c5468b95651fb4465500 Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Fri, 12 Jun 2020 17:53:56 -0700 Subject: prototype of tinycurses --- sys/libterm/buffer.c | 326 ++++++++++ sys/libterm/escseq.c | 0 sys/libterm/events.c | 1692 ++++++++++++++++++++++++++++++++++++++++++++++++++ sys/libterm/input.c | 108 ++++ sys/libterm/term.c | 395 ++++++++++++ sys/libterm/term.h | 489 +++++++++++++++ sys/libterm/window.c | 44 ++ 7 files changed, 3054 insertions(+) create mode 100644 sys/libterm/buffer.c create mode 100644 sys/libterm/escseq.c create mode 100644 sys/libterm/events.c create mode 100644 sys/libterm/input.c create mode 100644 sys/libterm/term.c create mode 100644 sys/libterm/term.h create mode 100644 sys/libterm/window.c (limited to 'sys/libterm') diff --git a/sys/libterm/buffer.c b/sys/libterm/buffer.c new file mode 100644 index 0000000..b903e71 --- /dev/null +++ b/sys/libterm/buffer.c @@ -0,0 +1,326 @@ +#include "term.h" + +/* row operations */ +void +zero(Row *row, int start, int len) +{ + int i; + Cell cell = { + .r = L'\0', + .pen = {0}, + }; + + for (i = start; i < len + start; i++) + row->cells[i] = cell; + row->dirty = true; +} + +void +roll(Row *start, Row *end, int count) +{ + int n = end - start; + + count %= n; + if (count < 0) + count += n; + + if (count) { + char buf[count * sizeof(Row)]; + memcpy(buf, start, count * sizeof(Row)); + memmove(start, start + count, (n - count) * sizeof(Row)); + memcpy(end - count, buf, count * sizeof(Row)); + for (Row *row = start; row < end; row++) + row->dirty = true; + } +} + +/* buffer operations */ +void +bclear(Buffer *b) +{ + int i; + Cell cell = { + .r = L'\0', + .pen = { + .state = PenNormal, + .col.fg = -1, + .col.bg = -1, + }, + }; + + for (i = 0; i < b->nrow; i++) { + Row *row = b->row + i; + for (int j = 0; j < b->ncol; j++) { + row->cells[j] = cell; + row->dirty = true; + } + } +} + +void +bfree(Buffer *b) +{ + int i; + + for (i = 0; i < b->nrow; i++) + free(b->row[i].cells); + + free(b->row); + + for (i = 0; i < b->scroll.size; i++) + free(b->scroll.buf[i].cells); + + free(b->scroll.buf); + free(b->tabs); +} + +void +bscroll(Buffer *b, int s) +{ + /* work in screenfuls */ + int ssz = b->scroll.bot - b->scroll.top; + + if (s > ssz) { + bscroll(b, ssz); + bscroll(b, s - ssz); + return; + } + if (s < -ssz) { + bscroll(b, -ssz); + bscroll(b, s + ssz); + return; + } + + b->scroll.above += s; + if (b->scroll.above >= b->scroll.size) + b->scroll.above = b->scroll.size; + + if (s > 0 && b->scroll.size) { + for (int i = 0; i < s; i++) { + Row tmp = b->scroll.top[i]; + b->scroll.top[i] = b->scroll.buf[b->scroll.index]; + b->scroll.buf[b->scroll.index] = tmp; + + b->scroll.index++; + if (b->scroll.index == b->scroll.size) + b->scroll.index = 0; + } + } + roll(b->scroll.top, b->scroll.bot, s); + if (s < 0 && b->scroll.size) { + for (int i = (-s) - 1; i >= 0; i--) { + b->scroll.index--; + if (b->scroll.index == -1) + b->scroll.index = b->scroll.size - 1; + + Row tmp = b->scroll.top[i]; + b->scroll.top[i] = b->scroll.buf[b->scroll.index]; + b->scroll.buf[b->scroll.index] = tmp; + b->scroll.top[i].dirty = true; + } + } +} + +void +bresize(Buffer *b, int nrow, int ncol) +{ + Row *row = b->row; + + if (b->nrow != nrow) { + if (b->crow >= row + nrow) { + /* scroll up instead of simply chopping off bottom */ + bscroll(b, (b->crow - b->row) - nrow + 1); + } + while (b->nrow > nrow) { + free(row[b->nrow - 1].cells); + b->nrow--; + } + + row = realloc(row, sizeof(Row) * nrow); + } + + if (b->maxcols < ncol) { + for (int r = 0; r < b->nrow; r++) { + row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol); + if (b->ncol < ncol) + zero(row + r, b->ncol, ncol - b->ncol); + row[r].dirty = true; + } + Row *sbuf = b->scroll.buf; + for (int r = 0; r < b->scroll.size; r++) { + sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol); + if (b->ncol < ncol) + zero(sbuf + r, b->ncol, ncol - b->ncol); + } + b->tabs = realloc(b->tabs, sizeof(*b->tabs) * ncol); + for (int c = b->ncol; c < ncol; c++) + b->tabs[c] = !(c & 7); + b->maxcols = ncol; + b->ncol = ncol; + } else if (b->ncol != ncol) { + for (int r = 0; r < b->nrow; r++) + row[r].dirty = true; + b->ncol = ncol; + } + + int deltarows = 0; + if (b->nrow < nrow) { + while (b->nrow < nrow) { + row[b->nrow].cells = calloc(b->maxcols, sizeof(Cell)); + zero(row + b->nrow, 0, b->maxcols); + b->nrow++; + } + + /* prepare for backfill */ + if (b->crow >= b->scroll.bot - 1) { + deltarows = b->row + nrow - b->crow - 1; + if (deltarows > b->scroll.above) + deltarows = b->scroll.above; + } + } + + b->crow += row - b->row; + b->scroll.top = row; + b->scroll.bot = row + nrow; + b->row = row; + + /* perform backfill */ + if (deltarows > 0) { + bscroll(b, -deltarows); + b->crow += deltarows; + } +} + +bool +binit(Buffer *b, int rows, int cols, int size) +{ + b->pen.state = PenNormal; + b->pen.col.fg = b->pen.col.fg = -1; + + if (size < 0) + size = 0; + if (size && !(b->scroll.buf = calloc(size, sizeof(Row)))) + return false; + + b->scroll.size = size; + bresize(b, rows, cols); + return true; +} + +void +bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) +{ + if (bs) + *bs = nil; + if (be) + *be = nil; + if (as) + *as = nil; + if (ae) + *ae = nil; + if (!b->scroll.size) + return; + + if (b->scroll.above) { + if (bs) + *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size]; + if (be) + *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size]; + } + if (b->scroll.below) { + if (as) + *as = &b->scroll.buf[b->scroll.index]; + if (ae) + *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size]; + } +} + +Row * +browfirst(Buffer *b) +{ + Row *bstart; + if (!b->scroll.size || !b->scroll.above) + return b->row; + bboundary(b, &bstart, nil, nil, nil); + return bstart; +} + +Row * +browlast(Buffer *b) +{ + Row *aend; + if (!b->scroll.size || !b->scroll.below) + return b->row + b->nrow - 1; + bboundary(b, nil, nil, nil, &aend); + return aend; +} + +Row * +brownext(Buffer *b, Row *row) +{ + Row *before_start, *before_end, *after_start, *after_end; + Row *first = b->row, *last = b->row + b->nrow - 1; + + if (!row) + return nil; + + bboundary(b, &before_start, &before_end, &after_start, &after_end); + + if (row >= first && row < last) + return ++row; + if (row == last) + return after_start; + if (row == before_end) + return first; + if (row == after_end) + return nil; + if (row == &b->scroll.buf[b->scroll.size - 1]) + return b->scroll.buf; + return ++row; +} + +Row * +bprevrow(Buffer *b, Row *row) +{ + Row *before_start, *before_end, *after_start, *after_end; + Row *first = b->row, *last = b->row + b->nrow - 1; + + if (!row) + return nil; + + bboundary(b, &before_start, &before_end, &after_start, &after_end); + + if (row > first && row <= last) + return --row; + if (row == first) + return before_end; + if (row == before_start) + return nil; + if (row == after_start) + return last; + if (row == b->scroll.buf) + return &b->scroll.buf[b->scroll.size - 1]; + return --row; +} + +void +brender(Buffer *b, Term *t) +{ + int r, c, n; + char u[UTFmax+1]; + Row *row; + Cell *cell; + + for (r = 0; r < b->nrow; r++) { + row = b->row + r; + if (!row->dirty) + continue; + + for (c = 0; c < b->ncol; c++) { + cell = row->cells + c; + tsetpen(t, cell->pen); + n = utf8·runetobyte(u, &cell->r); + twrite(t, n, u); + } + } +} diff --git a/sys/libterm/escseq.c b/sys/libterm/escseq.c new file mode 100644 index 0000000..e69de29 diff --git a/sys/libterm/events.c b/sys/libterm/events.c new file mode 100644 index 0000000..80bc99a --- /dev/null +++ b/sys/libterm/events.c @@ -0,0 +1,1692 @@ +#include "term.h" + +#include + +#define bufcount(in) in->buf.c - in->buf.b + +enum { + NodeKey, + NodeArr, +}; + +struct Node +{ + int type; +}; + +struct KeyNode +{ + struct Node; + struct KeyInfo key; +}; + +struct ArrNode +{ + struct Node; + uchar min, max; + Node *arr[]; +}; + +// ----------------------------------------------------------------------- +// loads data into trie + +static enum KeyEvent peekmousekey(Input *in, Key *key, ulong *nb); + +#define FuncNameMax 10 +static struct { + char *name; + int type; + int sym; + int mods; +} funcs[] = +{ + /* THIS LIST MUST REMAIN SORTED ALPHABETICALLY! */ + { "backspace", KeySym, SymBackspace, 0 }, + { "begin", KeySym, SymBegin, 0 }, + { "beg", KeySym, SymBegin, 0 }, + { "btab", KeySym, SymTab, ModShift}, + { "cancel", KeySym, SymCancel, 0 }, + { "clear", KeySym, SymClear, 0 }, + { "close", KeySym, SymClose, 0 }, + { "command", KeySym, SymCommand, 0 }, + { "copy", KeySym, SymCopy, 0 }, + { "dc", KeySym, SymDelete, 0 }, + { "down", KeySym, SymDown, 0 }, + { "end", KeySym, SymEnd, 0 }, + { "enter", KeySym, SymEnter, 0 }, + { "exit", KeySym, SymExit, 0 }, + { "find", KeySym, SymFind, 0 }, + { "help", KeySym, SymHelp, 0 }, + { "home", KeySym, SymHome, 0 }, + { "ic", KeySym, SymInsert, 0 }, + { "left", KeySym, SymLeft, 0 }, + { "mark", KeySym, SymMark, 0 }, + { "message", KeySym, SymMessage, 0 }, + { "move", KeySym, SymMove, 0 }, + { "next", KeySym, SymPagedown, 0 }, // Not quite, but it's the best we can do + { "npage", KeySym, SymPagedown, 0 }, + { "open", KeySym, SymOpen, 0 }, + { "options", KeySym, SymOptions, 0 }, + { "ppage", KeySym, SymPageup, 0 }, + { "previous", KeySym, SymPageup, 0 }, // Not quite, but it's the best we can do + { "print", KeySym, SymPrint, 0 }, + { "redo", KeySym, SymRedo, 0 }, + { "reference", KeySym, SymReference, 0 }, + { "refresh", KeySym, SymRefresh, 0 }, + { "replace", KeySym, SymReplace, 0 }, + { "restart", KeySym, SymRestart, 0 }, + { "resume", KeySym, SymResume, 0 }, + { "right", KeySym, SymRight, 0 }, + { "save", KeySym, SymSave, 0 }, + { "select", KeySym, SymSelect, 0 }, + { "suspend", KeySym, SymSuspend, 0 }, + { "undo", KeySym, SymUndo, 0 }, + { "up", KeySym, SymUp, 0 }, + { nil }, +}; + +// ----------------------------------------------------------------------- +// utility functions + +static +int +stricmp(char *s, char *t) +{ + if (s == nil) + if (t == nil) + return 0; + else + return -strlen(t); + else + if (t == nil) + return +strlen(s); + + int d; + for (; *s && *t; s++, t++) { + d = tolower(*s) - tolower(*t); + if (d < 0 || d > 0) + return d; + } + + /* XXX: not sure about passing in 0 here */ + return tolower(*s) - tolower(*t); +} + +// ----------------------------------------------------------------------- +// bytes -> keysymbols + +static +Node * +keynode(int type, int sym, int mask, int set) +{ + struct KeyNode *n; + n = malloc(sizeof(*n)); + if (!n) + panicf("out of memory"); + + n->type = NodeKey; + n->key.type = type; + n->key.sym = sym; + n->key.modmask = mask; + n->key.modset = set; + + return (Node *)n; +} + +static +Node * +arrnode(uchar min, uchar max) +{ + int nb, i; + struct ArrNode *n; + nb = ((int)max-min+1)*sizeof(*n->arr); + n = malloc(sizeof(*n) + nb); + if (!n) + panicf("out of memory"); + + n->type = NodeArr; + n->min = min, n->max = max; + for (i = 0; i <= max-min; i++) + n->arr[i] = nil; + + return (Node *)n; +} + + +static +Node * +nlookup(Node *n, uchar b) +{ + struct ArrNode *arr; + switch (n->type) { + case NodeKey: + panicf("attempting to subdivide a leaf key node"); + return nil; + case NodeArr: + arr = (struct ArrNode *)n; + if (b < arr->min || b > arr->max) + return nil; + return arr->arr[b - arr->min]; + default: + panicf("unrecognized key node type"); + return nil; + } +} + +static +Node * +compress(Node *root) +{ + int i; + uchar min, max; + struct ArrNode *n, *new; + + if (!root) + return root; + + switch (root->type) { + case NodeKey: + return root; + case NodeArr: + n = (struct ArrNode *)root; + /* find the zeros */ + for (min = 0; !n->arr[min]; min++) { + if (min == 255 && !n->arr[min]) { + free(n); + return arrnode(1, 0); + } + } + + for (max = 0xff; !n->arr[max]; max--) + ; + + new = (struct ArrNode *)arrnode(min, max); + + for (i = min; i <= max; i++) + new->arr[i-min] = compress(n->arr[i]); + + free(n); + return (Node*)new; + default: + panicf("unrecognized key node type"); + return nil; + } + + return root; +} + +static +void +teardown(Node *n) +{ + int i; + struct ArrNode *arr; + + switch (n->type) { + case NodeKey: + break; + case NodeArr: + arr = (struct ArrNode *)n; + for (i = arr->min; i <= arr->max; i++) + if (arr->arr[i - arr->min]) + teardown(arr->arr[i - arr->min]); + break; + default: + return; + } + + free(n); +} + +static +int +insert(Node *root, Node *n, char *seq) +{ + int pos; + uchar b; + Node *nn; + struct ArrNode *a; + + for (pos = 0; (b = seq[pos]); pos++) { + nn = nlookup(root, b); + if (!nn) + break; + root = nn; + a = (struct ArrNode *)root; + } + + for (; (b = seq[pos]); pos++) { + nn = (seq[pos+1]) ? arrnode(0, 0xff) : n; + if (!nn) + return 0; + + if (root->type != NodeArr) + panicf("inserted child node onto leaf 'key' node"); + + a = (struct ArrNode*)root; + if (b < a->min || b > a->max) + panicf("out of bound trie insertion"); + + a->arr[b-a->min] = root = nn; + } + + return 1; +} + +/* unibilium helpers */ +static +enum unibi_string +nametostr(char *name) +{ + enum unibi_string ret; + for (ret = unibi_string_begin_+1; ret < unibi_string_end_; ret++) + if (!strcmp(unibi_name_str(ret), name)) + return ret; + + return -1; +} + +static +char * +unibi_lookup(unibi_term *info, char *name) +{ + enum unibi_string idx; + if ((idx = nametostr(name)) == -1) + return nil; + + return (char*)unibi_get_str(info, idx); +} + +static +Node * +tryloadkey(unibi_term *info, char *name, char **val, struct KeyInfo *key) +{ + char *v; + + v = unibi_lookup(info, name); + if (!v || v[0] == 0) + return nil; + + *val = v; + return keynode(key->type, key->sym, key->modmask, key->modset); +} + +static +Node * +loadtermkeys(unibi_term *info) +{ + int i; + struct Node *root, *n; + char *val, name[5 + FuncNameMax + 1]; + + root = arrnode(0, 0xff); + if (!root) + panicf("out of memory"); + + /* load key syms */ + for (i = 0; funcs[i].name; i++) { + sprintf(name, "key_%s", funcs[i].name); + n = tryloadkey(info, name, &val, &(struct KeyInfo){ + .type = funcs[i].type, + .sym = funcs[i].sym, + .modmask = funcs[i].mods, + .modset = funcs[i].mods, + }); + + /* try shift modified */ + if (!n) + n = tryloadkey(info, name, &val, &(struct KeyInfo){ + .type = funcs[i].type, + .sym = funcs[i].sym, + .modmask = funcs[i].mods | ModShift, + .modset = funcs[i].mods | ModShift, + }); + + /* give up */ + if (n) + insert(root, n, val); + } + + /* load function keys */ + for (i = 1; i < 0xff; i++) { + sprintf(name, "key_f%d", i); + n = tryloadkey(info, name, &val, &(struct KeyInfo){ + .type = KeyFunc, + .sym = i, + .modmask = 0, + .modset = 0, + }); + if (!n) + break; + + insert(root, n, val); + } + + /* load mouse keys */ + val = unibi_lookup(info, "key_mouse"); + if (val && !strcmp(val, "\x1b[M")) { + n = keynode(KeyMouse, 0, 0, 0); + insert(root, n, val); + } + + return compress(root); +} + +static +enum KeyEvent +peeksym(Input *in, Key *key, int force, ulong *nb) +{ + int res; + uchar *b, *o; + Node *root; + struct KeyNode *kn; + + root = in->keys; + b = in->buf.b; + while (b <= in->buf.c) { + printf("checking '%s'\n", b); + root = nlookup(root, *b); + if (!root) + break; + b++; + + if (root->type != NodeKey) + continue; + + kn = (struct KeyNode*)root; + if (kn->key.type == KeyMouse) { + o = in->buf.b, in->buf.b = b; + res = peekmousekey(in, key, nb); + in->buf.b = o; + + if (res == EvKey) + *nb += b - in->buf.b; + + return res; + } + key->type = kn->key.type; + key->mods = kn->key.modset; + key->code.sym = kn->key.type; + *nb = b - in->buf.b; + + return EvKey; + } + + if (root && !force) + return EvAgain; + + return EvNil; +} + +// ----------------------------------------------------------------------- +// misc commands + +static +inline +void +setpos(Key *key, int row, int col) +{ + row = MIN(row, 0xfff); + col = MIN(col, 0xfff); + + key->code.mouse[1] = (row & 0x0ff); + key->code.mouse[2] = (col & 0x0ff); + key->code.mouse[3] = (row & 0xf00) >> 8 | (col & 0x300) >> 4; +} + +static +inline +void +getpos(Key *key, int *row, int *col) +{ + if (col) + *col = ((uchar)key->code.mouse[1] | ((uchar)key->code.mouse[3] & 0x0f) << 8) - 1; + + if (row) + *row = ((uchar)key->code.mouse[2] | ((uchar)key->code.mouse[3] & 0x70) << 4) - 1; +} + +// ----------------------------------------------------------------------- +// csi/ss3 commands + +static int csiinit; + +static struct KeyInfo ss3[64]; +static char ss3kpalts[64]; + +static struct KeyInfo csiss3[64]; +static enum KeyEvent (*do_csi[64])(Input *in, Key *key, int cmd, int narg, long *arg); + +static struct KeyInfo csifuncs[35]; + +/* csi/ss3 cmd keys */ +static +enum KeyEvent +do_csi_full(Input *in, Key *key, int cmd, int narg, long *arg) +{ + if(narg > 1 && arg[1] != -1) + key->mods = arg[1] - 1; + else + key->mods = 0; + + key->type = csiss3[cmd - 0x40].type; + key->code.sym = csiss3[cmd - 0x40].sym; + key->mods &= ~(csiss3[cmd - 0x40].modmask); + key->mods |= csiss3[cmd - 0x40].modset; + + if(key->code.sym == SymUnknown) + return EvNil; + + return EvKey; +} + +static +void +put_csi_full(int type, int sym, int modset, int modmask, uchar cmd) +{ + if(cmd < 0x40 || cmd >= 0x80) + return; + + csiss3[cmd - 0x40].type = type; + csiss3[cmd - 0x40].sym = sym; + csiss3[cmd - 0x40].modset = modset; + csiss3[cmd - 0x40].modmask = modmask; + + do_csi[cmd - 0x40] = &do_csi_full; +} + +/* ss3 kpad keys */ +static +void +put_ss3_kpalt(int type, int sym, uchar cmd, char kpalt) +{ + if(cmd < 0x40 || cmd >= 0x80) + return; + + ss3[cmd - 0x40].type = type; + ss3[cmd - 0x40].sym = sym; + ss3[cmd - 0x40].modset = 0; + ss3[cmd - 0x40].modmask= 0; + ss3kpalts[cmd - 0x40] = kpalt; +} + +/* csi number ~func keys */ +static void emitcodepoint(Input *in, rune r, Key *key); + +static +enum KeyEvent +do_csi_func(Input *in, Key *key, int cmd, int narg, long *arg) +{ + if (narg > 1 && arg[1] != -1) + key->mods = arg[1] - 1; + else + key->mods = 0; + + key->type = KeySym; + + if (arg[0] == 27) { + int mod = key->mods; + emitcodepoint(in, (rune)arg[2], key); + key->mods |= mod; + } else if (arg[0] >= 0 && arg[0] < arrlen(csifuncs)) { + key->type = csifuncs[arg[0]].type; + key->code.sym = csifuncs[arg[0]].sym; + key->mods &= ~(csifuncs[arg[0]].modmask); + key->mods |= csifuncs[arg[0]].modset; + } else + key->code.sym = SymUnknown; + + if (key->code.sym == SymUnknown) + return EvNil; + + return EvKey; +} + +static +void +put_csi_func(int type, int sym, int num) +{ + if(num >= arrlen(csifuncs)) + return; + + csifuncs[num].type = type; + csifuncs[num].sym = sym; + csifuncs[num].modset = 0; + csifuncs[num].modmask = 0; + + do_csi['~' - 0x40] = &do_csi_func; +} + +/* CSI u extended unicode keys */ +static +enum KeyEvent +do_csi_u(Input *in, Key *key, int cmd, int narg, long *arg) +{ + switch(cmd) { + case 'u': { + if(narg > 1 && arg[1] != -1) + key->mods = arg[1] - 1; + else + key->mods = 0; + + int mod = key->mods; + key->type = KeySym; + emitcodepoint(in, arg[0], key); + key->mods |= mod; + + return EvKey; + } + default: + return EvNil; + } +} + +/* csi m/M mouse events */ + +static +enum KeyEvent +do_csi_m(Input *in, Key *key, int cmd, int narg, long *arg) +{ + int initial = cmd >> 8; + cmd &= 0xff; + + switch(cmd) { + case 'M': + case 'm': + break; + default: + return EvNil; + } + + // rxvt protocol + if(!initial && narg >= 3) { + key->type = KeyMouse; + key->code.mouse[0] = arg[0]; + + key->mods = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + setpos(key, arg[1], arg[2]); + + return EvKey; + } + + // SGR protocol + if(initial == '<' && narg >= 3) { + key->type = KeyMouse; + key->code.mouse[0] = arg[0]; + + key->mods = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + setpos(key, arg[1], arg[2]); + + if(cmd == 'm') // release + key->code.mouse[3] |= 0x80; + + return EvKey; + } + + return EvNil; +} + +/* csi ? R position events */ + +static +enum KeyEvent +do_csi_R(Input *in, Key *key, int cmd, int narg, long *arg) +{ + switch(cmd) { + case 'R'|'?'<<8: + if(narg < 2) + return EvNil; + key->type = KeyPosition; + setpos(key, arg[1], arg[0]); + return EvKey; + default: + return do_csi_full(in, key, cmd, narg, arg); + } +} + +/* csi $y mode status events */ + +static +enum KeyEvent +do_csi_y(Input *in, Key *key, int cmd, int narg, long *arg) +{ + switch (cmd) { + case 'y'|'$'<<16: + case 'y'|'$'<<16 | '?'<<8: + if (narg < 2) + return EvNil; + + key->type = KeyModeReport; + key->code.mouse[0] = (cmd >> 8); + key->code.mouse[1] = arg[0] >> 8; + key->code.mouse[2] = arg[0] & 0xff; + key->code.mouse[3] = arg[1]; + return EvKey; + + default: + return EvNil; + } +} + +/* parse csi events */ +static +enum KeyEvent +parse_csi(Input *in, ulong introlen, ulong *csi_len, ulong *nargs, long args[], unsigned long *commandp) +{ + ulong csi_end = introlen; + + while (csi_end < bufcount(in)) { + if (in->buf.b[csi_end] >= 0x40 && in->buf.b[csi_end] < 0x80) + break; + csi_end++; + } + + if(csi_end >= bufcount(in)) + return EvAgain; + + uchar cmd = in->buf.b[csi_end]; + *commandp = cmd; + + char present = 0; + int argi = 0; + + ulong p = introlen; + + // See if there is an initial byte + if (in->buf.b[p] >= '<' && in->buf.b[p] <= '?') { + *commandp |= (in->buf.b[p] << 8); + p++; + } + + // Now attempt to parse out up number;number;... separated values + while (p < csi_end) { + uchar c = in->buf.b[p]; + + if (c >= '0' && c <= '9') { + if (!present) { + args[argi] = c - '0'; + present = 1; + } else { + args[argi] = (args[argi] * 10) + c - '0'; + } + } else if(c == ';') { + if (!present) + args[argi] = -1; + present = 0; + argi++; + + if(argi > 16) + break; + } else if (c >= 0x20 && c <= 0x2f) { + *commandp |= c << 16; + break; + } + p++; + } + + if(present) + argi++; + + *nargs = argi; + *csi_len = csi_end + 1; + + return EvKey; +} + +static +void +loadctrlkeys(void) +{ + int i; + for(i = 0; i < 64; i++) { + csiss3[i].sym = SymUnknown; + ss3[i].sym = SymUnknown; + ss3kpalts[i] = 0; + } + + for(i = 0; i < arrlen(csifuncs); i++) + csifuncs[i].sym = SymUnknown; + + put_csi_full(KeySym, SymUp, 0, 0, 'A'); + put_csi_full(KeySym, SymDown, 0, 0, 'B'); + put_csi_full(KeySym, SymRight,0, 0, 'C'); + put_csi_full(KeySym, SymLeft, 0, 0, 'D'); + put_csi_full(KeySym, SymBegin,0, 0, 'E'); + put_csi_full(KeySym, SymEnd, 0, 0, 'F'); + put_csi_full(KeySym, SymHome, 0, 0, 'H'); + put_csi_full(KeySym, 1, 0, 0, 'P'); + put_csi_full(KeySym, 2, 0, 0, 'Q'); + put_csi_full(KeySym, 3, 0, 0, 'R'); + put_csi_full(KeySym, 4, 0, 0, 'S'); + + put_csi_full(KeySym, SymTab, ModShift, ModShift, 'Z'); + + put_ss3_kpalt(KeySym, SymKpenter, 'M', 0); + put_ss3_kpalt(KeySym, SymKpequals, 'X', '='); + put_ss3_kpalt(KeySym, SymKpmult, 'j', '*'); + put_ss3_kpalt(KeySym, SymKpplus, 'k', '+'); + put_ss3_kpalt(KeySym, SymKpcomma, 'l', ','); + put_ss3_kpalt(KeySym, SymKpminus, 'm', '-'); + put_ss3_kpalt(KeySym, SymKpperiod, 'n', '.'); + put_ss3_kpalt(KeySym, SymKpdiv, 'o', '/'); + put_ss3_kpalt(KeySym, SymKp0, 'p', '0'); + put_ss3_kpalt(KeySym, SymKp1, 'q', '1'); + put_ss3_kpalt(KeySym, SymKp2, 'r', '2'); + put_ss3_kpalt(KeySym, SymKp3, 's', '3'); + put_ss3_kpalt(KeySym, SymKp4, 't', '4'); + put_ss3_kpalt(KeySym, SymKp5, 'u', '5'); + put_ss3_kpalt(KeySym, SymKp6, 'v', '6'); + put_ss3_kpalt(KeySym, SymKp7, 'w', '7'); + put_ss3_kpalt(KeySym, SymKp8, 'x', '8'); + put_ss3_kpalt(KeySym, SymKp9, 'y', '9'); + + put_csi_func(KeySym, SymFind, 1); + put_csi_func(KeySym, SymInsert, 2); + put_csi_func(KeySym, SymDelete, 3); + put_csi_func(KeySym, SymSelect, 4); + put_csi_func(KeySym, SymPageup, 5); + put_csi_func(KeySym, SymPagedown, 6); + put_csi_func(KeySym, SymHome, 7); + put_csi_func(KeySym, SymEnd, 8); + + put_csi_func(KeyFunc, 1, 11); + put_csi_func(KeyFunc, 2, 12); + put_csi_func(KeyFunc, 3, 13); + put_csi_func(KeyFunc, 4, 14); + put_csi_func(KeyFunc, 5, 15); + put_csi_func(KeyFunc, 6, 17); + put_csi_func(KeyFunc, 7, 18); + put_csi_func(KeyFunc, 8, 19); + put_csi_func(KeyFunc, 9, 20); + put_csi_func(KeyFunc, 10, 21); + put_csi_func(KeyFunc, 11, 23); + put_csi_func(KeyFunc, 12, 24); + put_csi_func(KeyFunc, 13, 25); + put_csi_func(KeyFunc, 14, 26); + put_csi_func(KeyFunc, 15, 28); + put_csi_func(KeyFunc, 16, 29); + put_csi_func(KeyFunc, 17, 31); + put_csi_func(KeyFunc, 18, 32); + put_csi_func(KeyFunc, 19, 33); + put_csi_func(KeyFunc, 20, 34); + + do_csi['u' - 0x40] = &do_csi_u; + do_csi['M' - 0x40] = &do_csi_m; + do_csi['m' - 0x40] = &do_csi_m; + do_csi['R' - 0x40] = &do_csi_R; + + do_csi['y' - 0x40] = &do_csi_y; + + csiinit = 1; +} + +static +enum KeyEvent +peekcsi(Input *in, ulong introlen, Key *key, int force, ulong *nb) +{ + ulong csi_len; + long arg[16]; + ulong nargs = arrlen(arg); + ulong cmd; + + enum KeyEvent ev = parse_csi(in, introlen, &csi_len, &nargs, arg, &cmd); + + if (ev== EvAgain) { + if(!force) + return ev; + + emitcodepoint(in, '[', key); + key->mods |= ModAlt; + *nb = introlen; + return EvKey; + } + // Mouse in X10 encoding consumes the next 3 bytes also + if (cmd == 'M' && nargs < 3) { + in->buf.b += csi_len; + ev = peekmousekey(in, key, nb); + in->buf.b -= csi_len; + + if (ev == EvKey) + *nb += csi_len; + + return ev; + } + + ev = EvNil; + + // We know from the logic above that cmd must be >= 0x40 and < 0x80 + if (do_csi[(cmd & 0xff) - 0x40]) + ev = (*do_csi[(cmd & 0xff) - 0x40])(in, key, cmd, nargs, arg); + + if (ev == EvNil) { + key->type = KeyUnknownCSI; + key->code.num = cmd; + key->mods = 0; + + in->buf.off = csi_len - introlen; + *nb = introlen; /* dont advance yet */ + return EvKey; + } + + *nb = csi_len; + return ev; +} + +static +enum KeyEvent +peekss3(Input *in, ulong introlen, Key *key, int force, ulong *nb) +{ + if(bufcount(in) < introlen + 1) { + if(!force) + return EvAgain; + + emitcodepoint(in, 'O', key); + key->mods |= ModAlt; + *nb= bufcount(in); + return EvKey; + } + uchar cmd = in->buf.b[introlen]; + + if(cmd < 0x40 || cmd >= 0x80) + return EvNil; + + key->type = csiss3[cmd - 0x40].type; + key->code.sym = csiss3[cmd - 0x40].sym; + key->mods = csiss3[cmd - 0x40].modset; + + if (key->code.sym == SymUnknown) { + if (in->flags & FlagConvertKP && ss3kpalts[cmd - 0x40]) { + key->type = KeyUnicode; + key->code.pt = ss3kpalts[cmd - 0x40]; + key->mods = 0; + + key->utf8[0] = key->code.pt; + key->utf8[1] = 0; + } else { + key->type = ss3[cmd - 0x40].type; + key->code.sym = ss3[cmd - 0x40].sym; + key->mods = ss3[cmd - 0x40].modset; + } + } + + if(key->code.sym == SymUnknown) + return EvNil; + + *nb = introlen + 1; + + return EvKey; +} + +static +enum KeyEvent +peekctrl(Input *in, ulong introlen, Key *key, int force, ulong *nb) +{ + ulong str_end = introlen; + + while(str_end < bufcount(in)) { + if (in->buf.b[str_end] == 0x9c) // ST + break; + if (in->buf.b[str_end] == 0x1b && + (str_end + 1) < bufcount(in) && + in->buf.b[str_end+1] == 0x5c) // ESC-prefixed ST + break; + + str_end++; + } + + if (str_end >= bufcount(in)) + return EvAgain; + + *nb = str_end + 1; + if(in->buf.b[str_end] == 0x1b) + (*nb)++; + + // XXX: read carefully + if(in->savedcsi) + free(in->savedcsi); + + ulong len = str_end - introlen; + + in->nsavedcsi++; + in->savedcsi = malloc(len + 1); + + strncpy(in->savedcsi, (char *)in->buf.b + introlen, len); + in->savedcsi[len] = 0; + + key->type = (in->buf.b[introlen-1] & 0x1f) == 0x10 ? KeyDCS : KeyOSC; + key->code.num = in->nsavedcsi; + key->mods = 0; + + return EvKey; +} + +static +enum KeyEvent +peekesc(Input *in, Key *key, int force, ulong *nb) +{ + if (bufcount(in) == 0) + return in->closed ? EvEOF : EvNil; + + switch (*in->buf.b) { + case 0x1b: + if(bufcount(in) < 2) + return EvNil; + + switch(in->buf.b[1]) { + case 0x4f: // ESC-prefixed SS3 + return peekss3(in, 2, key, force, nb); + + case 0x50: // ESC-prefixed DCS + case 0x5d: // ESC-prefixed OSC + return peekctrl(in, 2, key, force, nb); + + case 0x5b: // ESC-prefixed CSI + return peekcsi(in, 2, key, force, nb); + } + + return EvNil; + + case 0x8f: // SS3 + return peekss3(in, 1, key, force, nb); + + case 0x90: // DCS + case 0x9d: // OSC + return peekctrl(in, 1, key, force, nb); + + case 0x9b: // CSI + return peekcsi(in, 1, key, force, nb); + } + + return EvNil; +} + + +// ----------------------------------------------------------------------- +// internal functions + +static +int +registername(Input *in, int sym, char *name) +{ + if (!sym) + sym = in->nkeynm; + + if(sym >= in->nkeynm) { + char **tmp = realloc(in->keynm, sizeof(*tmp) * (sym + 1)); + if(!tmp) + return -1; + + in->keynm = tmp; + + // Fill in the hole + for(int i = in->nkeynm; i < sym; i++) + in->keynm[i] = nil; + + in->nkeynm = sym + 1; + } + + in->keynm[sym] = name; + + return sym; +} + +static +int +registerc0(Input *in, int sym, int modset, int modmask, uchar ctrl, char *name) +{ + if(ctrl >= 0x20) { + errno = EINVAL; + return -1; + } + + if (name) + sym = registername(in, sym, name); + + in->c0[ctrl].sym = sym; + in->c0[ctrl].modset = modset; + in->c0[ctrl].modmask = modmask; + + return sym; +} + +static +void +advance(Input *in, ulong nb) +{ + if (in->buf.c < in->buf.b + nb) { + in->buf.b = in->buf.c; + return; + } + + in->buf.b += nb; +} + +static +void +slidebuffer(Input *in) +{ + static const ulong halfway = arrlen(in->buf.bytes) / 2; + if (in->buf.b - in->buf.bytes > halfway) { + memmove(in->buf.bytes, in->buf.bytes + halfway, halfway); + in->buf.b -= halfway; + in->buf.c -= halfway; + } +} + +static +void +harmonize(Input *in, Key *key) +{ + int flags = in->hflag; + + if (flags & HarmonizeSpace) { + if (key->type == KeyUnicode && key->code.pt == 0x20) { + key->type = KeySym; + key->code.sym = SymSpace; + } + } else { + if (key->type == KeySym && key->code.sym == SymSpace) { + key->type = KeyUnicode; + key->code.pt = 0x20; + utf8·runetobyte((char*)key->utf8, &key->code.pt); + } + } + + if (flags & HarmonizeDelBS) { + if (key->type == KeySym && key->code.sym == SymDel) { + key->code.sym = SymBackspace; + } + } +} + +static +void +emitcodepoint(Input *in, rune r, Key *key) +{ + if (r == 0) { + key->type = KeySym; + key->code.sym = SymSpace; + key->mods = ModCtrl; + goto harmonize; + } + if (r < 0x20) { + key->code.pt = 0; + key->mods = 0; + if (!(in->flags & FlagNoInterpret) && in->c0[r].sym != SymUnknown) { + key->code.sym = in->c0[r].sym; + key->mods |= in->c0[r].modset; + } + if (!key->code.sym) { + key->type = KeyUnicode; + if (r+0x40 >= 'A' && r+0x40 <= 'Z') + // it's a letter - use lowercase instead + key->code.pt = r + 0x60; + else + key->code.pt = r + 0x40; + key->mods = ModCtrl; + } else + key->type = KeySym; + goto harmonize; + } + if (r == 0x7f && !(in->flags & FlagNoInterpret)) { + // ascii del + key->type = KeySym; + key->code.sym = SymDel; + key->mods = 0; + goto harmonize; + } + if (r >= 0x20 && r < 0x80) { + // ascii lowbyte range + key->type = KeyUnicode; + key->code.pt = r; + key->mods = 0; + goto harmonize; + } + if (r >= 0x80 && r < 0xa0) { + // UTF-8 never starts with a C1 byte. So we can be sure of these + key->type = KeyUnicode; + key->code.pt = r - 0x40; + key->mods = ModCtrl|ModAlt; + goto harmonize; + } + key->type = KeyUnicode; + key->code.pt = r; + key->mods = 0; + +harmonize: + harmonize(in, key); + utf8·runetobyte((char*)key->utf8, &key->code.pt); +} + +static +enum KeyEvent +peekmousekey(Input *in, Key *key, ulong *nb) +{ + if (in->buf.c - in->buf.b < 3) + return EvAgain; + + key->type = KeyMouse; + key->code.mouse[0] = in->buf.c[0] - 0x20; + key->code.mouse[1] = in->buf.c[1] - 0x20; + key->code.mouse[2] = in->buf.c[2] - 0x20; + key->code.mouse[3] = 0; + + key->mods = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + *nb = 3; + return EvKey; +} + +enum KeyEvent peekkey(Input *in, Key *key, int force, ulong *nb); + +static +enum KeyEvent +peeksimplekey(Input *in, Key *key, int force, ulong *nb) +{ + uchar c, *b; + int n; + rune r; + enum KeyEvent ev; + + b = in->buf.b; + c = *b; + + if (c == 0x1b) { + if (bufcount(in) == 1) { + if (!force) + return EvAgain; + goto ascii; + } + in->buf.b++; + ev = peekkey(in, key, force, nb); + in->buf.b--; + + switch (ev) { + case EvKey: + key->mods |= ModAlt; + (*nb)++; + /* fallthrough */ + case EvNil: case EvEOF: + case EvAgain: case EvErr: + return ev; + } + } + if (c == 0xa0) + goto ascii; + if (in->flags & FlagUTF8) { + n = utf8·bytetorune(&r, (char*)in->buf.b); + *nb = n; /* store the number of bytes */ + if (n > bufcount(in)) { + if (!force) + return EvAgain; + r = RuneErr; + *nb = bufcount(in); + } + key->type = KeyUnicode; + key->mods = 0; + goto utf8; + } + /* if we are here just emit raw byte */ + key->type = KeyUnicode; + key->code.pt = c; + key->mods = 0; + key->utf8[0] = c; + key->utf8[1] = 0; + *nb = 1; + return EvKey; + +ascii: + *nb = 1; + r = c; +utf8: + emitcodepoint(in, r, key); + return EvKey; +} + +// ----------------------------------------------------------------------- +// exported functions + +Input * +makeinput(int fd, int flags, unibi_term *info) +{ + int i; + Input *in; + char *e; + + if (!(in = malloc(sizeof(in)))) + panicf("out of memory"); + + in->fd = fd; + if (!(flags & (FlagRaw|FlagUTF8))) { + if (((e = getenv("LANG")) || (e = getenv("LC_MESSAGES")) || (e = getenv("LC_ALL"))) && + (e = strchr(e, '.')) && e++ && (!stricmp(e, "UTF-8") || !stricmp(e, "UTF8"))) + flags |= FlagUTF8; + else + flags |= FlagRaw; + } + in->flags = flags; + in->wait = 50; /* in msec */ + in->closed = 0; + in->started = 0; + in->hasold = 0; + in->keys = loadtermkeys(info); + + /* initialize buffer */ + in->buf.c = in->buf.b = in->buf.bytes; + in->buf.e = arrend(in->buf.bytes); + in->buf.off = 0; + memset(in->buf.bytes, 0, arrlen(in->buf.bytes)); + + /* initialize names */ + for (i = 0; i < 32; i++) + in->c0[i].sym = SymNone; + + registerc0(in, SymTab, 0x09, 0, 0, nil); + registerc0(in, SymEnter, 0x0d, 0, 0, nil); + registerc0(in, SymEscape, 0x1b, 0, 0, nil); + + /* load in csi */ + in->nsavedcsi = 0; + in->savedcsi = nil; + loadctrlkeys(); + + return in; +} + +void +freeinput(Input *in) +{ + // free(in); +} + +int +startrecord(Input *in) +{ + struct termios new; + if (in->started) + return 1; + + if (in->fd != -1 && !(in->flags & FlagNoTermIOS)) { + if (tcgetattr(in->fd, &new) == 0) { + in->oldterm = new; + in->hasold = 1; + + new.c_iflag &= ~(IXON|INLCR|ICRNL); + new.c_lflag &= ~(ICANON|ECHO|IEXTEN); + + new.c_cc[VMIN] = 1; + new.c_cc[VTIME] = 0; + + if (in->flags & FlagCtrlC) + new.c_lflag &= ~ISIG; + else { + /* Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT */ + new.c_cc[VQUIT] = _POSIX_VDISABLE; + new.c_cc[VSUSP] = _POSIX_VDISABLE; + /* Some OSes have Ctrl-Y==VDSUSP */ +# ifdef VDSUSP + new.c_cc[VDSUSP] = _POSIX_VDISABLE; +# endif + } + tcsetattr(in->fd, TCSANOW, &new); + } + } + + in->started = 1; + return 1; +} + +int +stoprecord(Input *in) +{ + if (!in->started) + return 1; + + if (in->hasold) + tcsetattr(in->fd, TCSANOW, &in->oldterm); + + in->started = 0; + return 1; +} + +enum KeyEvent +peekkey(Input *in, Key *key, int force, ulong *nb) +{ + int i, again = 0; + enum KeyEvent ev; + static enum KeyEvent (*peek[2])(Input *, Key *, int, ulong *) = { peeksym, peekesc }; + + if (!in->started) { + errno = EINVAL; + return EvErr; + } + + if (in->buf.off) { + in->buf.b += in->buf.off; + in->buf.off = 0; + } + + for (i = 0; i < arrlen(peek); i++) { + ev = peek[i](in, key, force, nb); + switch (ev) { + case EvKey: + slidebuffer(in); + /* fallthrough */ + case EvEOF: + case EvErr: + return ev; + case EvAgain: + if (!force) + again = 1; + /* fallthrough */ + case EvNil: + continue; + } + } + if (again) + return EvAgain; + + return peeksimplekey(in, key, force, nb); +} + +enum KeyEvent +getkey(Input *in, Key *key) +{ + ulong nb; + enum KeyEvent ev; + + ev = peekkey(in, key, 0, &nb); + switch (ev) { + case EvKey: + advance(in, nb); + break; + case EvAgain: + peekkey(in, key, 1, &nb); + /* get nb but don't advance */ + break; + default: + ; + } + return ev; +} + +enum KeyEvent +demandkey(Input *in, Key *key) +{ + ulong nb; + enum KeyEvent ev; + + ev = peekkey(in, key, 1, &nb); + if (ev == EvKey) + advance(in, nb); + + return ev; +} + +enum KeyEvent +isreadablekey(Input *in) +{ + int n; + if (in->fd == -1) { + errno = EBADF; + return EvErr; + } + + /* reset to beginning of buffer */ + if (in->buf.b > in->buf.bytes) { + n = in->buf.b - in->buf.bytes; + memmove(in->buf.bytes, in->buf.b, n); + in->buf.b = in->buf.bytes; + in->buf.c = in->buf.b + n; + } + +read: + n = read(in->fd, in->buf.c, in->buf.e-in->buf.c); + if (n == -1) { + if (errno == EAGAIN) + return EvNil; + if (errno == EINTR && !(in->flags & FlagEintr)) + goto read; + else + return EvErr; + } + if (n < 1) { + in->closed = 1; + return EvNil; + } + in->buf.c += n; + return EvAgain; +} + +enum KeyEvent +waitkey(Input *in, Key *key) +{ + enum KeyEvent ev; + struct pollfd p; + + if (in->fd == -1) { + errno = EBADF; + return EvErr; + } + + for (;;) { + ev = getkey(in, key); + switch (ev) { + case EvKey: case EvEOF: case EvErr: + return ev; + case EvNil: + ev = isreadablekey(in); + if (ev == EvErr) + return ev; + break; + + case EvAgain: + /* can't wait any longer */ + if (in->closed) + return demandkey(in, key); + poll: + p.fd = in->fd; + p.events = POLLIN; + + if (poll(&p, 1, in->wait) == -1) { + if (errno == EINTR && !(in->flags & FlagEintr)) + goto poll; + return EvErr; + } + + if (p.revents & (POLLIN|POLLHUP|POLLERR)) + ev = isreadablekey(in); + else + ev = EvNil; + + if (ev == EvErr) + return ev; + if (ev == EvNil) + return demandkey(in, key); + break; + } + } + /* unreachable */ +} + +enum KeyEvent +decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col) +{ + if(key->type != KeyMouse) + return EvNil; + + if(button) + *button = 0; + + getpos(key, row, col); + + if(!ev) + return EvKey; + + int btn = 0; + int code = key->code.mouse[0]; + int drag = code & 0x20; + code &= ~0x3c; + + switch(code) { + case 0: + case 1: + case 2: + *ev = drag ? MouseDrag : MousePress; + btn = code + 1; + break; + + case 3: + *ev = MouseRelease; + // no button hint + break; + + case 64: + case 65: + *ev = drag ? MouseDrag : MousePress; + btn = code + 4 - 64; + break; + + default: + *ev = MouseNil; + } + + if (button) + *button = btn; + + if (key->code.mouse[3] & 0x80) + *ev = MouseRelease; + + return EvKey; +} + +enum KeyEvent +decodepos(Input *in, Key *key, int *row, int *col) +{ + if (key->type != KeyPosition) + return EvNil; + + getpos(key, row, col); + + return EvKey; +} + +enum KeyEvent +decodemode(Input *in, Key *key, int *init, int *mode, int *val) +{ + if (key->type != KeyModeReport) + return EvNil; + + if (init) + *init = key->code.mouse[0]; + + if (mode) + *mode = (key->code.mouse[1] << 8) | key->code.mouse[2]; + + if (val) + *val = key->code.mouse[3]; + + return EvKey; +} + +char * +keyname(Input *in, int sym) +{ + if (sym == SymUnknown || sym >= in->nkeynm) + return ""; + + return in->keynm[sym]; +} + +// ----------------------------------------------------------------------- +// main point of entry + +static +void +printkey(Input *in, Key *key) +{ + enum MouseEvent ev; + int button, line, col; + int init, mode, val; + + switch(key->type) { + case KeyUnicode: + fprintf(stderr, "Unicode codepoint=U+%04dx utf8='%s'", key->code.pt, key->utf8); + break; + case KeyFunc: + fprintf(stderr, "Function F%d", key->code.num); + break; + case KeySym: + fprintf(stderr, "Keysym sym=%d(%s)", key->code.sym, keyname(in, key->code.sym)); + break; + case KeyMouse: + decodemouse(in, key, &ev, &button, &line, &col); + fprintf(stderr, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev, button, line, col); + break; + case KeyPosition: + decodepos(in, key, &line, &col); + fprintf(stderr, "Position report pos=(%d,%d)\n", line, col); + break; + case KeyModeReport: + decodemode(in, key, &init, &mode, &val); + fprintf(stderr, "Mode report mode=%s %d val=%d\n", init == '?' ? "DEC" : "ANSI", mode, val); + break; + case KeyDCS: + fprintf(stderr, "Device Control String"); + break; + case KeyOSC: + fprintf(stderr, "Operating System Control"); + break; + case KeyUnknownCSI: + fprintf(stderr, "unknown CSI\n"); + break; + } + + mode = key->mods; + fprintf(stderr, " mod=%s%s%s+%02x", + (mode & ModCtrl ? "" : ""), + (mode & ModAlt ? "" : ""), + (mode & ModShift? "" : ""), + mode & ~(ModCtrl|ModAlt|ModShift)); +} + +int +main() +{ + char *name; + Input *in; + unibi_term *info; + Key key; + enum KeyEvent ev; + + name = getenv("TERM"); + info = unibi_from_term(name); + in = makeinput(0, FlagSpaceSymbol, info); + + int n = 0; /* for debugging purposes only */ + startrecord(in); + while ((ev = waitkey(in, &key)) != EvEOF) { + switch (ev) { + case EvKey: + printkey(in, &key); + printf("\n"); + break; + case EvNil: + printf("\n"); + case EvAgain: + printf("\n"); + default: + ; + } + n++; + if (n > 200) + break; + } + stoprecord(in); + freeinput(in); +} diff --git a/sys/libterm/input.c b/sys/libterm/input.c new file mode 100644 index 0000000..038e8df --- /dev/null +++ b/sys/libterm/input.c @@ -0,0 +1,108 @@ +#include +#include +#include + +#define iota(x) 1 << (x) + +// ----------------------------------------------------------------------- +// enums + +enum +{ + KeyRune, KeyFunc, KeySym, + KeyMouse, KeyPosition, KeyModeReport, + KeyDCS, KeyOSC, /* add other recognised types here */ + KeyUnknownCSI = -1 +}; + +enum KeyEvent +{ + EvNil, EvKey, EvEOF, EvAgain, EvErr, +}; + +enum +{ + SymUnknown = -1, + SymNone = 0, + + /* special names in c0 */ + SymBackspace, SymTab, SymEnter, SymEscape, + + /* special names in g0 */ + SymSpace, SymDel, + + /* special keys */ + SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert, + SymDelete, SymSelect, SymPageUp, SymPageDown, SymHome, SymEnd, + + /* special keys from terminfo */ + SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit, + SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions, + SymPrint, SymRedo, SymReference, SymRefresh, SymReplace, + SymRestart, SymResume, SymSave, SymSuspend, SymUndo, + + /* numeric keypad special keys */ + SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8, + SymKp9, SymKpEnter, SymKpPlus, SymKpMinus, SymKpMul, SymKpDiv, SymKpComma, + SymKpDot, SymKpEq, + + /* et cetera ad nauseum */ + NumSyms +}; + +enum MouseEvent +{ + MouseNil, MousePress, MouseDrag, MouseRelease, +}; + +enum +{ + ModShift = iota(0), + ModAlt = iota(1), + ModCtrl = iota(2), +}; + +// ----------------------------------------------------------------------- +// types + +typedef struct Input Input; + +struct Input +{ + int fd; + int wait; /* in msec */ + struct { + char *b, *c, bytes[1024]; + } rbuf; /* read buffer */ + struct { + char *b, *c, bytes[1024]; + } ebuf; /* escape buffer */ +}; + +// ----------------------------------------------------------------------- +// globals + +// ----------------------------------------------------------------------- +// utility functions + +// ----------------------------------------------------------------------- +// implementation + +Input * +makeinput(int fd, int flags, unibi_term *info) +{ + int i; + Input *in; + char *e; + + if (!(in = calloc(1, sizeof(in)))) + panicf("out of memory"); + + in->fd = fd; + in->wait = 50; /* in msec */ + + /* initialize buffers */ + in->rbuf.c = in->rbuf.b = in->rbuf.bytes; + + return in; +} diff --git a/sys/libterm/term.c b/sys/libterm/term.c new file mode 100644 index 0000000..cda2bbe --- /dev/null +++ b/sys/libterm/term.c @@ -0,0 +1,395 @@ +#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); +} diff --git a/sys/libterm/term.h b/sys/libterm/term.h new file mode 100644 index 0000000..acae95f --- /dev/null +++ b/sys/libterm/term.h @@ -0,0 +1,489 @@ +#pragma once + +#include +#include + +#include +#include + +#define iota(x) 1 << (x) +/* + * obtained from: + * https://invisible-island.net/ncurses/man/curs_add_wch.3x.html + */ +#define ACSRUNES \ + /* name utf8 ascii acsc*/ \ + ACSRUNE("block", 0x25ae, '#', '0') \ + ACSRUNE("board", 0x2592, '#', 'h') \ + ACSRUNE("btee", 0x2534, '+', 'v') \ + ACSRUNE("bullet", 0x00b7, 'o', '~') \ + ACSRUNE("ckboard", 0x2592, ':', 'a') \ + ACSRUNE("darrow", 0x2193, 'v', '.') \ + ACSRUNE("degree", 0x00b0, '\'','f') \ + ACSRUNE("diamond", 0x25c6, '+', '`') \ + ACSRUNE("gequal", 0x2265, '>', '>') \ + ACSRUNE("hline", 0x2500, '-', 'q') \ + ACSRUNE("antern", 0x2603, '#', 'i') \ + ACSRUNE("larrow", 0x2190, '<', ',') \ + ACSRUNE("lequal", 0x2264, '<', 'y') \ + ACSRUNE("llcorner", 0x2514, '+', 'm') \ + ACSRUNE("lrcorner", 0x2518, '+', 'j') \ + ACSRUNE("ltee", 0x2524, '+', 't') \ + ACSRUNE("nequal", 0x2260, '!', '|') \ + ACSRUNE("pi", 0x03c0, '*', '{') \ + ACSRUNE("plminus", 0x00b1, '#', 'g') \ + ACSRUNE("plus", 0x253c, '+', 'n') \ + ACSRUNE("rarrow", 0x2192, '>', '+') \ + ACSRUNE("rtee", 0x251c, '+', 'u') \ + ACSRUNE("s1", 0x23ba, '-', 'o') \ + ACSRUNE("s3", 0x23bb, '-', 'p') \ + ACSRUNE("s7", 0x23bc, '-', 'r') \ + ACSRUNE("s9", 0x23bd, '_', 's') \ + ACSRUNE("sterling", 0x00a3, 'f', '}') \ + ACSRUNE("ttee", 0x252c, '+', 'w') \ + ACSRUNE("uarrow", 0x2191, '^', '-') \ + ACSRUNE("ulcorner", 0x250c, '+', 'l') \ + ACSRUNE("urcorner", 0x2510, '+', 'k') \ + ACSRUNE("vline", 0x2502, '|', 'x') \ + /* thick versions */ \ + ACSRUNE("t_btee", 0x253b, '+', 'V') \ + ACSRUNE("t_hline", 0x2501, '-', 'Q') \ + ACSRUNE("t_llcorner", 0x2517, '+', 'M') \ + ACSRUNE("t_lrcorner", 0x251b, '+', 'J') \ + ACSRUNE("t_ltee", 0x252b, '+', 'T') \ + ACSRUNE("t_plus", 0x254b, '+', 'N') \ + ACSRUNE("t_rtee", 0x2523, '+', 'U') \ + ACSRUNE("t_ttee", 0x2533, '+', 'W') \ + ACSRUNE("t_ulcorner", 0x250f, '+', 'L') \ + ACSRUNE("t_urcorner", 0x2513, '+', 'K') \ + ACSRUNE("t_vline", 0x2503, '|', 'X') \ + /* double version */ \ + ACSRUNE("d_btee", 0x2569, '+', 'H') \ + ACSRUNE("d_hline", 0x2550, '-', 'R') \ + ACSRUNE("d_llcorner", 0x255a, '+', 'D') \ + ACSRUNE("d_lrcorner", 0x255d, '+', 'A') \ + ACSRUNE("d_ltee", 0x2560, '+', 'F') \ + ACSRUNE("d_plus", 0x256c, '+', 'E') \ + ACSRUNE("d_rtee", 0x2563, '+', 'G') \ + ACSRUNE("d_ttee", 0x2566, '+', 'I') \ + ACSRUNE("d_ulcorner", 0x2554, '+', 'C') \ + ACSRUNE("d_urcorner", 0x2557, '+', 'B') \ + ACSRUNE("d_vline", 0x2551, '|', 'Y') + +/* enums */ + +/* key symbols */ +enum +{ + SymUnknown = -1, + SymNone = 0, + + /* special names in c0 */ + SymBackspace, SymTab, SymEnter, SymEscape, + + /* special names in g0 */ + SymSpace, SymDel, + + /* special keys */ + SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert, + SymDelete, SymSelect, SymPageup, SymPagedown, SymHome, SymEnd, + + /* special keys from terminfo */ + SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit, + SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions, + SymPrint, SymRedo, SymReference, SymRefresh, SymReplace, + SymRestart, SymResume, SymSave, SymSuspend, SymUndo, + + /* numeric keypad special keys */ + SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8, + SymKp9, SymKpenter, SymKpplus, SymKpminus, SymKpmult, SymKpdiv, SymKpcomma, + SymKpperiod, SymKpequals, + + /* et cetera ad nauseum */ + NumSyms +}; + +/* key type */ +enum +{ + KeyUnicode, + KeyFunc, + KeySym, + KeyMouse, + KeyPosition, + KeyModeReport, + KeyDCS, + KeyOSC, + /* add other recognised types here */ + + KeyUnknownCSI = -1 +}; + +/* key events */ +enum KeyEvent +{ + EvNil, + EvKey, + EvEOF, + EvAgain, + EvErr, +}; + +enum MouseEvent +{ + MouseNil, + MousePress, + MouseDrag, + MouseRelease, +}; + +enum +{ + ModShift = iota(0), + ModAlt = iota(1), + ModCtrl = iota(2), +}; + +enum +{ + FlagNoInterpret = iota(0), + FlagConvertKP = iota(1), + FlagRaw = iota(2), + FlagUTF8 = iota(3), + FlagNoTermIOS = iota(4), + FlagSpaceSymbol = iota(5), + FlagCtrlC = iota(6), + FlagEintr = iota(7), +}; + +enum +{ + HarmonizeSpace = iota(0), + HarmonizeDelBS = iota(1), +}; + +/* types */ +typedef struct RGB8 RGB8; +typedef struct Pen Pen; + +typedef struct Cell Cell; +typedef struct Row Row; +typedef struct Buffer Buffer; +typedef struct Window Window; + +typedef struct Node Node; +typedef struct Key Key; +typedef struct Input Input; + +typedef struct Term Term; + +struct RGB8 +{ + uint8 r, g, b; +}; + +enum +{ + PenNormal = 0, + PenBold = iota(0), + PenDim = iota(1), + PenInvis = iota(2), + PenItalic = iota(3), + PenReverse = iota(4), + PenStrike = iota(5), + PenUnderline = iota(6), + PenBlink = iota(7), + /* ... */ + PenTrueClr = iota(15), +}; + +struct Pen +{ + ushort state; + union { + /* 256 color (legacy) */ + struct { + sshort fg : 8, bg : 8; /* 0 - 255 or COLOUR_DEFAULT */ + } col; + /* true color (modern) */ + struct { + RGB8 fg, bg; + } rgb; + }; +}; + +/* outputs */ +struct Cell { + rune r; + Pen pen; +}; + +struct Row { + Cell *cells; + uint dirty:1; +}; + +/* Buffer holding the current window content (as an array) as well + * as the scroll back buffer content (as a circular/ring buffer). + * + * If new content is added to terminal the view port slides down and the + * previously top most line is moved into the scroll back buffer at postion + * scroll.index. This index will eventually wrap around and thus overwrite + * the oldest lines. + * + * In the scenerio below a scroll up has been performed. That is 'scroll.above' + * lines still lie above the current view port. Further scrolling up will show + * them. Similarly 'scroll.below' is the amount of lines below the current + * viewport. + * + * The function buffer_boundary sets the row pointers to the start/end range + * of the section delimiting the region before/after the viewport. The functions + * buffer_row_{first,last} return the first/last logical row. And + * buffer_row_{next,prev} allows to iterate over the logical lines in either + * direction. + * + * scroll back buffer + * + * scroll_buf->+----------------+-----+ + * | | | ^ \ + * | before | | | | + * current terminal content | viewport | | | | + * | | | | + * +----------------+-----+\ | | | s > scroll.above + * ^ | | i | \ | | i | c | + * | | | n | \ | | n | r | + * | | v | \ | | v | o | + * r | | i | \ | | i | l / + * o | viewport | s | >|<- scroll.index | s | l \ + * w | | i | / | | i | | + * s | | b | / | after | b | s > scroll.below + * | | l | / | viewport | l | i | + * v | | e | / | | e | z / + * +----------------+-----+/ | unused | | e + * <- maxcols -> | scroll back | | + * <- cols -> | buffer | | | + * | | | | + * | | | v + * roll_buf + scroll.size->+----------------+-----+ + * <- maxcols -> + * <- cols -> + */ + +struct Buffer { + Row *row; /* array of Row pointers of size 'rows' */ + Row *crow; /* row on which the cursor currently resides */ + bool *tabs; /* a boolean flag for each column whether it is a tab */ + struct { + Row *buf; /* a ring buffer holding the scroll back content */ + Row *top; /* row in lines where scrolling region starts */ + Row *bot; /* row in lines where scrolling region ends */ + int size; /* maximal capacity of scroll back buffer (in lines) */ + int index; /* current index into the ring buffer */ + int above; /* number of lines above current viewport */ + int below; /* number of lines below current viewport */ + } scroll; + Pen pen, spen; + int nrow, ncol; /* current dimension of buffer */ + int maxcols; /* allocated cells (maximal cols over time) */ + int srow, scol; /* saved cursor row/column (zero based) */ +}; + +/* input */ +struct Key +{ + int type; + int mods; + uchar utf8[UTFmax+1]; + union { + rune pt; + int num; + int sym; + char mouse[4]; + } code; +}; + +struct KeyInfo +{ + int type; + int sym; + int modmask; + int modset; +}; + +/* make opaque? */ +struct Input +{ + int fd; + int flags; + int hflag; + int wait; /* in ms */ + + struct termios oldterm; + + /* buffer */ + struct { + long off; + uchar *b, *c, *e, bytes[256]; + } buf; + + /* modifiers */ + char closed : 1; + char started : 1; + char hasold : 1; + + /* key data */ + Node *keys; + struct KeyInfo c0[32]; + + char **keynm; + int nkeynm; + + int nsavedcsi; + char *savedcsi; +}; + +struct Term +{ + /* meta data */ + char *name; + unibi_term *info; + struct { + uint altscreen : 1; + uint cursorvis : 1; + uint mouse : 1; + } mode; + struct { + uint bce : 1; + int colors; + } cap; + + /* input capture */ + Input *input; + + /* output display */ + Window *root; + Pen pen; + + /* output raw text */ + int fd; + struct { + char *c, b[512]; + } buf; + + struct { + int len; + char *b; + } tmp; + + /* info */ + struct { + /* Positioning */ + char *cup; // cursor_address + char *vpa; // row_address == vertical position absolute + char *hpa; // column_address = horizontal position absolute + + /* Moving */ + char *cuu; char *cuu1; // Cursor Up + char *cud; char *cud1; // Cursor Down + char *cuf; char *cuf1; // Cursor Forward == Right + char *cub; char *cub1; // Cursor Backward == Left + + /* Editing */ + char *ich; char *ich1; // Insert Character + char *dch; char *dch1; // Delete Character + char *il; char *il1; // Insert Line + char *dl; char *dl1; // Delete Line + char *ech; // Erase Character + char *ed2; // Erase Data 2 == Clear screen + char *stbm; // Set Top/Bottom Margins + + /* formatting */ + char *sgr; // Select Graphic Rendition + char *sgr0; // Exit Attribute Mode + char *sgr_i0, *sgr_i1; // SGR italic off/on + char *sgr_fg; // SGR foreground colour + char *sgr_bg; // SGR background colour + + /* Mode setting/clearing */ + char *sm_csr; char *rm_csr; // Set/reset mode: Cursor visible + + /* augmentations to terminfo */ + struct { + char *rgbf; // rgb foreground + char *rgbb; // rgb background + char *smxx; // strikethrough + char *smulx; // curly underline + } ext; + } esc; + + /* basic shapes */ + struct { + rune block; + rune board; + rune hline; + rune vline; + rune plus; + rune ltee; + rune rtee; + rune ttee; + rune btee; + rune ulcorner; + rune urcorner; + rune llcorner; + rune lrcorner; + } acs; +}; + +/* + * exported functions + */ + +#if 0 +/* buffer.c */ +void zero(Row *row, int start, int len); +void roll(Row *start, Row *end, int count); + +void bclear(Buffer *b); +void bfree(Buffer *b); +void bscroll(Buffer *b, int s); +void bresize(Buffer *b, int rows, int cols); +bool binit(Buffer *b, int rows, int cols, int size); +void brender(Buffer *b, Term *t); +void bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) ; +Row *browfirst(Buffer *b); +Row *browlast(Buffer *b); +Row *brownext(Buffer *b, Row *row); +Row *bprevrow(Buffer *b, Row *row); + +/* input.c */ +Input *makeinput(int fd, int flags, unibi_term *info); +void freeinput(Input *in); +int startrecord(Input *in); +int stoprecord(Input *in); +char *keyname(Input *in, int sym); + +enum KeyEvent term·waitkey(Input *in, Key *key); /* block until next keypress */ +enum KeyEvent term·getkey(Input *in, Key *key); /* grab key if we can */ +enum KeyEvent term·demandkey(Input *in, Key *key); /* grab now and interpret as best we can */ + +/* unpack key event into useful data */ +enum KeyEvent decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col); +enum KeyEvent decodepos(Input *in, Key *key, int *row, int *col); +enum KeyEvent decodemode(Input *in, Key *key, int *init, int *mode, int *val); +#endif + +/* term.c */ +void term·init(Term *t); +void term·fini(Term *t); +void term·flush(Term *t); +void term·write(Term *t, long len, char *s); +int term·goto(Term *t, int row, int col); +void term·jump(Term *t, int down, int right); +void term·del(Term *t, int num); +void term·setpen(Term *t, Pen pen); +void term·clear(Term *t); + +/* input.c */ +enum KeyEvent term·waitkey(Term *t, Key *key); /* block until next keypress */ +enum KeyEvent term·getkey(Term *t, Key *key); /* grab key if we can */ +enum KeyEvent term·demandkey(Term *t, Key *key); /* grab now and interpret as best we can */ diff --git a/sys/libterm/window.c b/sys/libterm/window.c new file mode 100644 index 0000000..fec3997 --- /dev/null +++ b/sys/libterm/window.c @@ -0,0 +1,44 @@ +#include +#include + +#include "term.h" + +typedef struct Rect Rect; + +struct Rect +{ + int top, left, rows, cols; +}; + +struct Window +{ + Buffer buffer[2], *buf; + Rect area; /* on screen */ + Pen pen, spen; /* current and saved pen */ + uint curvis : 1; + uint damage : 1; +}; + +/* functions */ + +Window * +makewindow(Window *root, Rect area, int history) +{ + Window *w; + w = calloc(1, sizeof(*w)); + if (!w) + panicf("out of memory"); + + w->pen = (Pen) { + .state = PenNormal, + .col = {-1, -1}, + }; + + if (!binit(w->buffer+0, area.rows, area.cols, history) || + !binit(w->buffer+1, area.rows, area.cols, 0)) { + free(w); + return nil; + } + w->buf = w->buffer; + return w; +} -- cgit v1.2.1