aboutsummaryrefslogtreecommitdiff
path: root/sys/libterm
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2020-06-12 17:53:56 -0700
committerNicholas Noll <nbnoll@eml.cc>2020-06-12 17:53:56 -0700
commit6f84b07d939be6ef5e50c5468b95651fb4465500 (patch)
tree8b0ae09f9e051a5ee97d0b323072c7231e15e12f /sys/libterm
parent6db18fdf24d4f91f208618de03a9ade8d21dc999 (diff)
prototype of tinycurses
Diffstat (limited to 'sys/libterm')
-rw-r--r--sys/libterm/buffer.c326
-rw-r--r--sys/libterm/escseq.c0
-rw-r--r--sys/libterm/events.c1692
-rw-r--r--sys/libterm/input.c108
-rw-r--r--sys/libterm/term.c395
-rw-r--r--sys/libterm/term.h489
-rw-r--r--sys/libterm/window.c44
7 files changed, 3054 insertions, 0 deletions
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
--- /dev/null
+++ b/sys/libterm/escseq.c
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 <poll.h>
+
+#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 "<unknown>";
+
+ 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 ? "<C>" : ""),
+ (mode & ModAlt ? "<A>" : ""),
+ (mode & ModShift? "<S>" : ""),
+ 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("<nil>\n");
+ case EvAgain:
+ printf("<again>\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 <u.h>
+#include <libn.h>
+#include <unibilium.h>
+
+#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 <u.h>
+#include <libn.h>
+
+#include <termios.h>
+#include <unibilium.h>
+
+#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 <u.h>
+#include <libn.h>
+
+#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;
+}