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/events.c | 1692 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1692 insertions(+) create mode 100644 sys/libterm/events.c (limited to 'sys/libterm/events.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 + +#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); +} -- cgit v1.2.1