diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | driver-csi.c | 582 | ||||
-rw-r--r-- | termkey-internal.h | 39 | ||||
-rw-r--r-- | termkey.c | 572 |
4 files changed, 635 insertions, 562 deletions
@@ -7,10 +7,10 @@ endif all: demo -demo: termkey.o demo.c +demo: termkey.o driver-csi.o demo.c gcc $(CCFLAGS) $(LDFLAGS) -o $@ $^ -termkey.o: termkey.c +%.o: %.c gcc $(CCFLAGS) -o $@ -c $^ .PHONY: clean diff --git a/driver-csi.c b/driver-csi.c new file mode 100644 index 0000000..a3e3e7e --- /dev/null +++ b/driver-csi.c @@ -0,0 +1,582 @@ +#include "termkey.h" +#include "termkey-internal.h" + +#include <stdio.h> +#include <string.h> + +struct keyinfo { + termkey_type type; + termkey_keysym sym; + int modifier_mask; + int modifier_set; +}; + +typedef struct { + // There are 32 C0 codes + struct keyinfo c0[32]; + + // There are 64 codes 0x40 - 0x7F + struct keyinfo csi_ss3s[64]; + struct keyinfo ss3s[64]; + char ss3_kpalts[64]; + + int ncsifuncs; + struct keyinfo *csifuncs; +} termkey_csi; + +void *termkeycsi_new_driver(termkey_t *tk) +{ + termkey_csi *csi = malloc(sizeof *csi); + + int i; + + for(i = 0; i < 32; i++) { + csi->c0[i].sym = TERMKEY_SYM_UNKNOWN; + } + + for(i = 0; i < 64; i++) { + csi->csi_ss3s[i].sym = TERMKEY_SYM_UNKNOWN; + csi->ss3s[i].sym = TERMKEY_SYM_UNKNOWN; + csi->ss3_kpalts[i] = 0; + } + + csi->ncsifuncs = 32; + + csi->csifuncs = malloc(sizeof(csi->csifuncs[0]) * csi->ncsifuncs); + + for(i = 0; i < csi->ncsifuncs; i++) + csi->csifuncs[i].sym = TERMKEY_SYM_UNKNOWN; + + return csi; +} + +void termkeycsi_free_driver(void *private) +{ + termkey_csi *csi = private; + + free(csi->csifuncs); csi->csifuncs = NULL; + + free(csi); +} + +static inline void eatbytes(termkey_t *tk, size_t count) +{ + if(count >= tk->buffcount) { + tk->buffstart = 0; + tk->buffcount = 0; + return; + } + + tk->buffstart += count; + tk->buffcount -= count; + + size_t halfsize = tk->buffsize / 2; + + if(tk->buffstart > halfsize) { + memcpy(tk->buffer, tk->buffer + halfsize, halfsize); + tk->buffstart -= halfsize; + } +} + +#define UTF8_INVALID 0xFFFD + +static int utf8_seqlen(int codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +static void fill_utf8(termkey_key *key) +{ + int codepoint = key->code.codepoint; + int nbytes = utf8_seqlen(codepoint); + + key->utf8[nbytes] = 0; + + // This is easier done backwards + int b = nbytes; + while(b > 1) { + b--; + key->utf8[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: key->utf8[0] = (codepoint & 0x7f); break; + case 2: key->utf8[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: key->utf8[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: key->utf8[0] = 0xf0 | (codepoint & 0x07); break; + case 5: key->utf8[0] = 0xf8 | (codepoint & 0x03); break; + case 6: key->utf8[0] = 0xfc | (codepoint & 0x01); break; + } +} + +static inline void do_codepoint(termkey_t *tk, int codepoint, termkey_key *key) +{ + termkey_csi *csi = tk->driver_info; + + if(codepoint < 0x20) { + // C0 range + key->code.codepoint = 0; + key->modifiers = 0; + + if(!(tk->flags & TERMKEY_FLAG_NOINTERPRET) && csi->c0[codepoint].sym != TERMKEY_SYM_UNKNOWN) { + key->code.sym = csi->c0[codepoint].sym; + key->modifiers |= csi->c0[codepoint].modifier_set; + } + + if(!key->code.sym) { + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint + 0x40; + key->modifiers = TERMKEY_KEYMOD_CTRL; + } + else { + key->type = TERMKEY_TYPE_KEYSYM; + } + } + else if(codepoint == 0x20 && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { + // ASCII space + key->type = TERMKEY_TYPE_KEYSYM; + key->code.sym = TERMKEY_SYM_SPACE; + key->modifiers = 0; + } + else if(codepoint == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { + // ASCII DEL + key->type = TERMKEY_TYPE_KEYSYM; + key->code.sym = TERMKEY_SYM_DEL; + key->modifiers = 0; + } + else if(codepoint >= 0x20 && codepoint < 0x80) { + // ASCII lowbyte range + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint; + key->modifiers = 0; + } + else if(codepoint >= 0x80 && codepoint < 0xa0) { + // UTF-8 never starts with a C1 byte. So we can be sure of these + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint - 0x40; + key->modifiers = TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT; + } + else { + // UTF-8 codepoint + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint; + key->modifiers = 0; + } + + if(key->type == TERMKEY_TYPE_UNICODE) + fill_utf8(key); +} + +#define CHARAT(i) (tk->buffer[tk->buffstart + (i)]) + +static termkey_result getkey_csi(termkey_t *tk, size_t introlen, termkey_key *key) +{ + termkey_csi *csi = tk->driver_info; + + size_t csi_end = introlen; + + while(csi_end < tk->buffcount) { + if(CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) + break; + csi_end++; + } + + if(csi_end >= tk->buffcount) { + if(tk->waittime) + return TERMKEY_RES_AGAIN; + + do_codepoint(tk, '[', key); + key->modifiers |= TERMKEY_KEYMOD_ALT; + eatbytes(tk, introlen); + return TERMKEY_RES_KEY; + } + + unsigned char cmd = CHARAT(csi_end); + int arg[16]; + char present = 0; + int args = 0; + + size_t p = introlen; + + // Now attempt to parse out up number;number;... separated values + while(p < csi_end) { + unsigned char c = CHARAT(p); + + if(c >= '0' && c <= '9') { + if(!present) { + arg[args] = c - '0'; + present = 1; + } + else { + arg[args] = (arg[args] * 10) + c - '0'; + } + } + else if(c == ';') { + if(!present) + arg[args] = -1; + present = 0; + args++; + + if(args > 16) + break; + } + + p++; + } + + if(!present) + arg[args] = -1; + + args++; + + eatbytes(tk, csi_end + 1); + + if(args > 1 && arg[1] != -1) + key->modifiers = arg[1] - 1; + else + key->modifiers = 0; + + if(cmd == '~') { + key->type = TERMKEY_TYPE_KEYSYM; + + if(arg[0] == 27) { + int mod = key->modifiers; + do_codepoint(tk, arg[2], key); + key->modifiers |= mod; + } + else if(arg[0] >= 0 && arg[0] < csi->ncsifuncs) { + key->type = csi->csifuncs[arg[0]].type; + key->code.sym = csi->csifuncs[arg[0]].sym; + key->modifiers &= ~(csi->csifuncs[arg[0]].modifier_mask); + key->modifiers |= csi->csifuncs[arg[0]].modifier_set; + } + else + key->code.sym = TERMKEY_SYM_UNKNOWN; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) + fprintf(stderr, "CSI function key %d\n", arg[0]); + } + else { + // We know from the logic above that cmd must be >= 0x40 and < 0x80 + key->type = csi->csi_ss3s[cmd - 0x40].type; + key->code.sym = csi->csi_ss3s[cmd - 0x40].sym; + key->modifiers &= ~(csi->csi_ss3s[cmd - 0x40].modifier_mask); + key->modifiers |= csi->csi_ss3s[cmd - 0x40].modifier_set; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) + fprintf(stderr, "CSI arg1=%d arg2=%d cmd=%c\n", arg[0], arg[1], cmd); + } + + return TERMKEY_RES_KEY; +} + +static termkey_result getkey_ss3(termkey_t *tk, size_t introlen, termkey_key *key) +{ + termkey_csi *csi = tk->driver_info; + + if(tk->buffcount < introlen + 1) { + if(tk->waittime) + return TERMKEY_RES_AGAIN; + + do_codepoint(tk, 'O', key); + key->modifiers |= TERMKEY_KEYMOD_ALT; + eatbytes(tk, tk->buffcount); + return TERMKEY_RES_KEY; + } + + unsigned char cmd = CHARAT(introlen); + + eatbytes(tk, introlen + 1); + + if(cmd < 0x40 || cmd >= 0x80) + return TERMKEY_SYM_UNKNOWN; + + key->type = csi->csi_ss3s[cmd - 0x40].type; + key->code.sym = csi->csi_ss3s[cmd - 0x40].sym; + key->modifiers = csi->csi_ss3s[cmd - 0x40].modifier_set; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) { + if(tk->flags & TERMKEY_FLAG_CONVERTKP && csi->ss3_kpalts[cmd - 0x40]) { + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = csi->ss3_kpalts[cmd - 0x40]; + key->modifiers = 0; + + key->utf8[0] = key->code.codepoint; + key->utf8[1] = 0; + + return TERMKEY_RES_KEY; + } + + key->type = csi->ss3s[cmd - 0x40].type; + key->code.sym = csi->ss3s[cmd - 0x40].sym; + key->modifiers = csi->ss3s[cmd - 0x40].modifier_set; + } + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) + fprintf(stderr, "SS3 %c (0x%02x)\n", cmd, cmd); + + return TERMKEY_RES_KEY; +} + +termkey_result termkeycsi_getkey(termkey_t *tk, termkey_key *key) +{ + if(tk->buffcount == 0) + return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; + + // Now we're sure at least 1 byte is valid + unsigned char b0 = CHARAT(0); + + if(b0 == 0x1b) { + if(tk->buffcount == 1) { + // This might be an <Esc> press, or it may want to be part of a longer + // sequence + if(tk->waittime) + return TERMKEY_RES_AGAIN; + + do_codepoint(tk, b0, key); + eatbytes(tk, 1); + return TERMKEY_RES_KEY; + } + + unsigned char b1 = CHARAT(1); + + if(b1 == '[') + return getkey_csi(tk, 2, key); + + if(b1 == 'O') + return getkey_ss3(tk, 2, key); + + if(b1 == 0x1b) { + do_codepoint(tk, b0, key); + eatbytes(tk, 1); + return TERMKEY_RES_KEY; + } + + tk->buffstart++; + + termkey_result metakey_result = termkey_getkey(tk, key); + + switch(metakey_result) { + case TERMKEY_RES_KEY: + key->modifiers |= TERMKEY_KEYMOD_ALT; + tk->buffstart--; + eatbytes(tk, 1); + break; + + case TERMKEY_RES_NONE: + case TERMKEY_RES_EOF: + case TERMKEY_RES_AGAIN: + break; + } + + return metakey_result; + } + else if(b0 == 0x8f) { + return getkey_ss3(tk, 1, key); + } + else if(b0 == 0x9b) { + return getkey_csi(tk, 1, key); + } + else if(b0 < 0xa0) { + // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte + do_codepoint(tk, b0, key); + eatbytes(tk, 1); + return TERMKEY_RES_KEY; + } + else if(tk->flags & TERMKEY_FLAG_UTF8) { + // Some UTF-8 + int nbytes; + int codepoint; + + key->type = TERMKEY_TYPE_UNICODE; + key->modifiers = 0; + + if(b0 < 0xc0) { + // Starts with a continuation byte - that's not right + do_codepoint(tk, UTF8_INVALID, key); + eatbytes(tk, 1); + return TERMKEY_RES_KEY; + } + else if(b0 < 0xe0) { + nbytes = 2; + codepoint = b0 & 0x1f; + } + else if(b0 < 0xf0) { + nbytes = 3; + codepoint = b0 & 0x0f; + } + else if(b0 < 0xf8) { + nbytes = 4; + codepoint = b0 & 0x07; + } + else if(b0 < 0xfc) { + nbytes = 5; + codepoint = b0 & 0x03; + } + else if(b0 < 0xfe) { + nbytes = 6; + codepoint = b0 & 0x01; + } + else { + do_codepoint(tk, UTF8_INVALID, key); + eatbytes(tk, 1); + return TERMKEY_RES_KEY; + } + + if(tk->buffcount < nbytes) + return tk->waittime ? TERMKEY_RES_AGAIN : TERMKEY_RES_NONE; + + for(int b = 1; b < nbytes; b++) { + unsigned char cb = CHARAT(b); + if(cb < 0x80 || cb >= 0xc0) { + do_codepoint(tk, UTF8_INVALID, key); + eatbytes(tk, b - 1); + return TERMKEY_RES_KEY; + } + + codepoint <<= 6; + codepoint |= cb & 0x3f; + } + + // Check for overlong sequences + if(nbytes > utf8_seqlen(codepoint)) + codepoint = UTF8_INVALID; + + // Check for UTF-16 surrogates or invalid codepoints + if((codepoint >= 0xD800 && codepoint <= 0xDFFF) || + codepoint == 0xFFFE || + codepoint == 0xFFFF) + codepoint = UTF8_INVALID; + + do_codepoint(tk, codepoint, key); + eatbytes(tk, nbytes); + return TERMKEY_RES_KEY; + } + else { + // Non UTF-8 case - just report the raw byte + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = b0; + key->modifiers = 0; + + key->utf8[0] = key->code.codepoint; + key->utf8[1] = 0; + + eatbytes(tk, 1); + + return TERMKEY_RES_KEY; + } + + return TERMKEY_SYM_NONE; +} + +termkey_keysym termkey_register_c0(termkey_t *tk, termkey_keysym sym, unsigned char ctrl, const char *name) +{ + return termkey_register_c0_full(tk, sym, 0, 0, ctrl, name); +} + +termkey_keysym termkey_register_c0_full(termkey_t *tk, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char ctrl, const char *name) +{ + termkey_csi *csi = tk->driver_info; + + if(ctrl >= 0x20) { + fprintf(stderr, "Cannot register C0 key at ctrl 0x%02x - out of bounds\n", ctrl); + return -1; + } + + if(name) + sym = termkey_register_keyname(tk, sym, name); + + csi->c0[ctrl].sym = sym; + csi->c0[ctrl].modifier_set = modifier_set; + csi->c0[ctrl].modifier_mask = modifier_mask; + + return sym; +} + +termkey_keysym termkey_register_csi_ss3(termkey_t *tk, termkey_type type, termkey_keysym sym, unsigned char cmd, const char *name) +{ + return termkey_register_csi_ss3_full(tk, type, sym, 0, 0, cmd, name); +} + +termkey_keysym termkey_register_csi_ss3_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char cmd, const char *name) +{ + termkey_csi *csi = tk->driver_info; + + if(cmd < 0x40 || cmd >= 0x80) { + fprintf(stderr, "Cannot register CSI/SS3 key at cmd 0x%02x - out of bounds\n", cmd); + return -1; + } + + if(name) + sym = termkey_register_keyname(tk, sym, name); + + csi->csi_ss3s[cmd - 0x40].type = type; + csi->csi_ss3s[cmd - 0x40].sym = sym; + csi->csi_ss3s[cmd - 0x40].modifier_set = modifier_set; + csi->csi_ss3s[cmd - 0x40].modifier_mask = modifier_mask; + + return sym; +} + +termkey_keysym termkey_register_ss3kpalt(termkey_t *tk, termkey_type type, termkey_keysym sym, unsigned char cmd, const char *name, char kpalt) +{ + return termkey_register_ss3kpalt_full(tk, type, sym, 0, 0, cmd, name, kpalt); +} + +termkey_keysym termkey_register_ss3kpalt_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char cmd, const char *name, char kpalt) +{ + termkey_csi *csi = tk->driver_info; + + if(cmd < 0x40 || cmd >= 0x80) { + fprintf(stderr, "Cannot register SS3 key at cmd 0x%02x - out of bounds\n", cmd); + return -1; + } + + if(name) + sym = termkey_register_keyname(tk, sym, name); + + csi->ss3s[cmd - 0x40].type = type; + csi->ss3s[cmd - 0x40].sym = sym; + csi->ss3s[cmd - 0x40].modifier_set = modifier_set; + csi->ss3s[cmd - 0x40].modifier_mask = modifier_mask; + csi->ss3_kpalts[cmd - 0x40] = kpalt; + + return sym; +} + +termkey_keysym termkey_register_csifunc(termkey_t *tk, termkey_type type, termkey_keysym sym, int number, const char *name) +{ + return termkey_register_csifunc_full(tk, type, sym, 0, 0, number, name); +} + +termkey_keysym termkey_register_csifunc_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, int number, const char *name) +{ + termkey_csi *csi = tk->driver_info; + + if(name) + sym = termkey_register_keyname(tk, sym, name); + + if(number >= csi->ncsifuncs) { + struct keyinfo *new_csifuncs = realloc(csi->csifuncs, sizeof(new_csifuncs[0]) * (number + 1)); + csi->csifuncs = new_csifuncs; + + // Fill in the hole + for(int i = csi->ncsifuncs; i < number; i++) + csi->csifuncs[i].sym = TERMKEY_SYM_UNKNOWN; + + csi->ncsifuncs = number + 1; + } + + csi->csifuncs[number].type = type; + csi->csifuncs[number].sym = sym; + csi->csifuncs[number].modifier_set = modifier_set; + csi->csifuncs[number].modifier_mask = modifier_mask; + + return sym; +} diff --git a/termkey-internal.h b/termkey-internal.h new file mode 100644 index 0000000..e9fe1fd --- /dev/null +++ b/termkey-internal.h @@ -0,0 +1,39 @@ +#ifndef GUARD_TERMKEY_INTERNAL_H_ +#define GUARD_TERMKEY_INTERNAL_H_ + +#include "termkey.h" + +#include <stdint.h> +#include <termios.h> + +struct termkey_driver +{ + void *(*new_driver)(void); + void (*free_driver)(void *); +}; + +struct termkey { + int fd; + int flags; + unsigned char *buffer; + size_t buffstart; // First offset in buffer + size_t buffcount; // NUMBER of entires valid in buffer + size_t buffsize; // Total malloc'ed size + + struct termios restore_termios; + char restore_termios_valid; + + int waittime; // msec + + char is_closed; + + int nkeynames; + const char **keynames; + + struct termkey_driver driver; + void *driver_info; +}; + +void *termkeycsi_new_driver(termkey_t *t); + +#endif @@ -1,49 +1,18 @@ #include "termkey.h" +#include "termkey-internal.h" #include <errno.h> #include <poll.h> #include <unistd.h> #include <string.h> -#include <termios.h> #include <stdio.h> -struct keyinfo { - termkey_type type; - termkey_keysym sym; - int modifier_mask; - int modifier_set; -}; - -struct termkey { - int fd; - int flags; - unsigned char *buffer; - size_t buffstart; // First offset in buffer - size_t buffcount; // NUMBER of entires valid in buffer - size_t buffsize; // Total malloc'ed size - - struct termios restore_termios; - char restore_termios_valid; - - int waittime; // msec - - char is_closed; - - int nkeynames; - const char **keynames; - - // There are 32 C0 codes - struct keyinfo c0[32]; - - // There are 64 codes 0x40 - 0x7F - struct keyinfo csi_ss3s[64]; - struct keyinfo ss3s[64]; - char ss3_kpalts[64]; - - int ncsifuncs; - struct keyinfo *csifuncs; -}; +// TODO: Move these into t->driver +void *termkeycsi_new_driver(termkey_t *tk); +void termkeycsi_free_driver(void *private); +termkey_result termkeycsi_getkey(termkey_t *tk, termkey_key *key); +// END TODO termkey_t *termkey_new_full(int fd, int flags, size_t buffsize, int waittime) { @@ -89,30 +58,14 @@ termkey_t *termkey_new_full(int fd, int flags, size_t buffsize, int waittime) tk->is_closed = 0; - int i; - - for(i = 0; i < 32; i++) { - tk->c0[i].sym = TERMKEY_SYM_UNKNOWN; - } - - for(i = 0; i < 64; i++) { - tk->csi_ss3s[i].sym = TERMKEY_SYM_UNKNOWN; - tk->ss3s[i].sym = TERMKEY_SYM_UNKNOWN; - tk->ss3_kpalts[i] = 0; - } - - // These numbers aren't too important; buffers will be grown if insufficient tk->nkeynames = 64; - tk->ncsifuncs = 32; - tk->keynames = malloc(sizeof(tk->keynames[0]) * tk->nkeynames); - tk->csifuncs = malloc(sizeof(tk->csifuncs[0]) * tk->ncsifuncs); + int i; for(i = 0; i < tk->nkeynames; i++) tk->keynames[i] = NULL; - for(i = 0; i < tk->ncsifuncs; i++) - tk->csifuncs[i].sym = TERMKEY_SYM_UNKNOWN; + tk->driver_info = termkeycsi_new_driver(tk); // Special built-in names termkey_register_keyname(tk, TERMKEY_SYM_NONE, "NONE"); @@ -214,7 +167,9 @@ void termkey_free(termkey_t *tk) { free(tk->buffer); tk->buffer = NULL; free(tk->keynames); tk->keynames = NULL; - free(tk->csifuncs); tk->csifuncs = NULL; + + termkeycsi_free_driver(tk->driver_info); + tk->driver_info = NULL; /* Be nice to GC'ers, etc */ free(tk); } @@ -237,414 +192,9 @@ int termkey_getwaittime(termkey_t *tk) return tk->waittime; } -static inline void eatbytes(termkey_t *tk, size_t count) -{ - if(count >= tk->buffcount) { - tk->buffstart = 0; - tk->buffcount = 0; - return; - } - - tk->buffstart += count; - tk->buffcount -= count; - - size_t halfsize = tk->buffsize / 2; - - if(tk->buffstart > halfsize) { - memcpy(tk->buffer, tk->buffer + halfsize, halfsize); - tk->buffstart -= halfsize; - } -} - -#define UTF8_INVALID 0xFFFD - -static int utf8_seqlen(int codepoint) -{ - if(codepoint < 0x0000080) return 1; - if(codepoint < 0x0000800) return 2; - if(codepoint < 0x0010000) return 3; - if(codepoint < 0x0200000) return 4; - if(codepoint < 0x4000000) return 5; - return 6; -} - -static void fill_utf8(termkey_key *key) -{ - int codepoint = key->code.codepoint; - int nbytes = utf8_seqlen(codepoint); - - key->utf8[nbytes] = 0; - - // This is easier done backwards - int b = nbytes; - while(b > 1) { - b--; - key->utf8[b] = 0x80 | (codepoint & 0x3f); - codepoint >>= 6; - } - - switch(nbytes) { - case 1: key->utf8[0] = (codepoint & 0x7f); break; - case 2: key->utf8[0] = 0xc0 | (codepoint & 0x1f); break; - case 3: key->utf8[0] = 0xe0 | (codepoint & 0x0f); break; - case 4: key->utf8[0] = 0xf0 | (codepoint & 0x07); break; - case 5: key->utf8[0] = 0xf8 | (codepoint & 0x03); break; - case 6: key->utf8[0] = 0xfc | (codepoint & 0x01); break; - } -} - -static inline void do_codepoint(termkey_t *tk, int codepoint, termkey_key *key) -{ - if(codepoint < 0x20) { - // C0 range - key->code.codepoint = 0; - key->modifiers = 0; - - if(!(tk->flags & TERMKEY_FLAG_NOINTERPRET) && tk->c0[codepoint].sym != TERMKEY_SYM_UNKNOWN) { - key->code.sym = tk->c0[codepoint].sym; - key->modifiers |= tk->c0[codepoint].modifier_set; - } - - if(!key->code.sym) { - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint + 0x40; - key->modifiers = TERMKEY_KEYMOD_CTRL; - } - else { - key->type = TERMKEY_TYPE_KEYSYM; - } - } - else if(codepoint == 0x20 && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { - // ASCII space - key->type = TERMKEY_TYPE_KEYSYM; - key->code.sym = TERMKEY_SYM_SPACE; - key->modifiers = 0; - } - else if(codepoint == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { - // ASCII DEL - key->type = TERMKEY_TYPE_KEYSYM; - key->code.sym = TERMKEY_SYM_DEL; - key->modifiers = 0; - } - else if(codepoint >= 0x20 && codepoint < 0x80) { - // ASCII lowbyte range - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint; - key->modifiers = 0; - } - else if(codepoint >= 0x80 && codepoint < 0xa0) { - // UTF-8 never starts with a C1 byte. So we can be sure of these - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint - 0x40; - key->modifiers = TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT; - } - else { - // UTF-8 codepoint - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint; - key->modifiers = 0; - } - - if(key->type == TERMKEY_TYPE_UNICODE) - fill_utf8(key); -} - -#define CHARAT(i) (tk->buffer[tk->buffstart + (i)]) - -static termkey_result getkey_csi(termkey_t *tk, size_t introlen, termkey_key *key) -{ - size_t csi_end = introlen; - - while(csi_end < tk->buffcount) { - if(CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) - break; - csi_end++; - } - - if(csi_end >= tk->buffcount) { - if(tk->waittime) - return TERMKEY_RES_AGAIN; - - do_codepoint(tk, '[', key); - key->modifiers |= TERMKEY_KEYMOD_ALT; - eatbytes(tk, introlen); - return TERMKEY_RES_KEY; - } - - unsigned char cmd = CHARAT(csi_end); - int arg[16]; - char present = 0; - int args = 0; - - size_t p = introlen; - - // Now attempt to parse out up number;number;... separated values - while(p < csi_end) { - unsigned char c = CHARAT(p); - - if(c >= '0' && c <= '9') { - if(!present) { - arg[args] = c - '0'; - present = 1; - } - else { - arg[args] = (arg[args] * 10) + c - '0'; - } - } - else if(c == ';') { - if(!present) - arg[args] = -1; - present = 0; - args++; - - if(args > 16) - break; - } - - p++; - } - - if(!present) - arg[args] = -1; - - args++; - - eatbytes(tk, csi_end + 1); - - if(args > 1 && arg[1] != -1) - key->modifiers = arg[1] - 1; - else - key->modifiers = 0; - - if(cmd == '~') { - key->type = TERMKEY_TYPE_KEYSYM; - - if(arg[0] == 27) { - int mod = key->modifiers; - do_codepoint(tk, arg[2], key); - key->modifiers |= mod; - } - else if(arg[0] >= 0 && arg[0] < tk->ncsifuncs) { - key->type = tk->csifuncs[arg[0]].type; - key->code.sym = tk->csifuncs[arg[0]].sym; - key->modifiers &= ~(tk->csifuncs[arg[0]].modifier_mask); - key->modifiers |= tk->csifuncs[arg[0]].modifier_set; - } - else - key->code.sym = TERMKEY_SYM_UNKNOWN; - - if(key->code.sym == TERMKEY_SYM_UNKNOWN) - fprintf(stderr, "CSI function key %d\n", arg[0]); - } - else { - // We know from the logic above that cmd must be >= 0x40 and < 0x80 - key->type = tk->csi_ss3s[cmd - 0x40].type; - key->code.sym = tk->csi_ss3s[cmd - 0x40].sym; - key->modifiers &= ~(tk->csi_ss3s[cmd - 0x40].modifier_mask); - key->modifiers |= tk->csi_ss3s[cmd - 0x40].modifier_set; - - if(key->code.sym == TERMKEY_SYM_UNKNOWN) - fprintf(stderr, "CSI arg1=%d arg2=%d cmd=%c\n", arg[0], arg[1], cmd); - } - - return TERMKEY_RES_KEY; -} - -static termkey_result getkey_ss3(termkey_t *tk, size_t introlen, termkey_key *key) -{ - if(tk->buffcount < introlen + 1) { - if(tk->waittime) - return TERMKEY_RES_AGAIN; - - do_codepoint(tk, 'O', key); - key->modifiers |= TERMKEY_KEYMOD_ALT; - eatbytes(tk, tk->buffcount); - return TERMKEY_RES_KEY; - } - - unsigned char cmd = CHARAT(introlen); - - eatbytes(tk, introlen + 1); - - if(cmd < 0x40 || cmd >= 0x80) - return TERMKEY_SYM_UNKNOWN; - - key->type = tk->csi_ss3s[cmd - 0x40].type; - key->code.sym = tk->csi_ss3s[cmd - 0x40].sym; - key->modifiers = tk->csi_ss3s[cmd - 0x40].modifier_set; - - if(key->code.sym == TERMKEY_SYM_UNKNOWN) { - if(tk->flags & TERMKEY_FLAG_CONVERTKP && tk->ss3_kpalts[cmd - 0x40]) { - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = tk->ss3_kpalts[cmd - 0x40]; - key->modifiers = 0; - - key->utf8[0] = key->code.codepoint; - key->utf8[1] = 0; - - return TERMKEY_RES_KEY; - } - - key->type = tk->ss3s[cmd - 0x40].type; - key->code.sym = tk->ss3s[cmd - 0x40].sym; - key->modifiers = tk->ss3s[cmd - 0x40].modifier_set; - } - - if(key->code.sym == TERMKEY_SYM_UNKNOWN) - fprintf(stderr, "SS3 %c (0x%02x)\n", cmd, cmd); - - return TERMKEY_RES_KEY; -} - termkey_result termkey_getkey(termkey_t *tk, termkey_key *key) { - if(tk->buffcount == 0) - return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; - - // Now we're sure at least 1 byte is valid - unsigned char b0 = CHARAT(0); - - if(b0 == 0x1b) { - if(tk->buffcount == 1) { - // This might be an <Esc> press, or it may want to be part of a longer - // sequence - if(tk->waittime) - return TERMKEY_RES_AGAIN; - - do_codepoint(tk, b0, key); - eatbytes(tk, 1); - return TERMKEY_RES_KEY; - } - - unsigned char b1 = CHARAT(1); - - if(b1 == '[') - return getkey_csi(tk, 2, key); - - if(b1 == 'O') - return getkey_ss3(tk, 2, key); - - if(b1 == 0x1b) { - do_codepoint(tk, b0, key); - eatbytes(tk, 1); - return TERMKEY_RES_KEY; - } - - tk->buffstart++; - - termkey_result metakey_result = termkey_getkey(tk, key); - - switch(metakey_result) { - case TERMKEY_RES_KEY: - key->modifiers |= TERMKEY_KEYMOD_ALT; - tk->buffstart--; - eatbytes(tk, 1); - break; - - case TERMKEY_RES_NONE: - case TERMKEY_RES_EOF: - case TERMKEY_RES_AGAIN: - break; - } - - return metakey_result; - } - else if(b0 == 0x8f) { - return getkey_ss3(tk, 1, key); - } - else if(b0 == 0x9b) { - return getkey_csi(tk, 1, key); - } - else if(b0 < 0xa0) { - // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte - do_codepoint(tk, b0, key); - eatbytes(tk, 1); - return TERMKEY_RES_KEY; - } - else if(tk->flags & TERMKEY_FLAG_UTF8) { - // Some UTF-8 - int nbytes; - int codepoint; - - key->type = TERMKEY_TYPE_UNICODE; - key->modifiers = 0; - - if(b0 < 0xc0) { - // Starts with a continuation byte - that's not right - do_codepoint(tk, UTF8_INVALID, key); - eatbytes(tk, 1); - return TERMKEY_RES_KEY; - } - else if(b0 < 0xe0) { - nbytes = 2; - codepoint = b0 & 0x1f; - } - else if(b0 < 0xf0) { - nbytes = 3; - codepoint = b0 & 0x0f; - } - else if(b0 < 0xf8) { - nbytes = 4; - codepoint = b0 & 0x07; - } - else if(b0 < 0xfc) { - nbytes = 5; - codepoint = b0 & 0x03; - } - else if(b0 < 0xfe) { - nbytes = 6; - codepoint = b0 & 0x01; - } - else { - do_codepoint(tk, UTF8_INVALID, key); - eatbytes(tk, 1); - return TERMKEY_RES_KEY; - } - - if(tk->buffcount < nbytes) - return tk->waittime ? TERMKEY_RES_AGAIN : TERMKEY_RES_NONE; - - for(int b = 1; b < nbytes; b++) { - unsigned char cb = CHARAT(b); - if(cb < 0x80 || cb >= 0xc0) { - do_codepoint(tk, UTF8_INVALID, key); - eatbytes(tk, b - 1); - return TERMKEY_RES_KEY; - } - - codepoint <<= 6; - codepoint |= cb & 0x3f; - } - - // Check for overlong sequences - if(nbytes > utf8_seqlen(codepoint)) - codepoint = UTF8_INVALID; - - // Check for UTF-16 surrogates or invalid codepoints - if((codepoint >= 0xD800 && codepoint <= 0xDFFF) || - codepoint == 0xFFFE || - codepoint == 0xFFFF) - codepoint = UTF8_INVALID; - - do_codepoint(tk, codepoint, key); - eatbytes(tk, nbytes); - return TERMKEY_RES_KEY; - } - else { - // Non UTF-8 case - just report the raw byte - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = b0; - key->modifiers = 0; - - key->utf8[0] = key->code.codepoint; - key->utf8[1] = 0; - - eatbytes(tk, 1); - - return TERMKEY_RES_KEY; - } - - return TERMKEY_SYM_NONE; + return termkeycsi_getkey(tk, key); } termkey_result termkey_getkey_force(termkey_t *tk, termkey_key *key) @@ -758,104 +308,6 @@ const char *termkey_get_keyname(termkey_t *tk, termkey_keysym sym) return "UNKNOWN"; } -termkey_keysym termkey_register_c0(termkey_t *tk, termkey_keysym sym, unsigned char ctrl, const char *name) -{ - return termkey_register_c0_full(tk, sym, 0, 0, ctrl, name); -} - -termkey_keysym termkey_register_c0_full(termkey_t *tk, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char ctrl, const char *name) -{ - if(ctrl >= 0x20) { - fprintf(stderr, "Cannot register C0 key at ctrl 0x%02x - out of bounds\n", ctrl); - return -1; - } - - if(name) - sym = termkey_register_keyname(tk, sym, name); - - tk->c0[ctrl].sym = sym; - tk->c0[ctrl].modifier_set = modifier_set; - tk->c0[ctrl].modifier_mask = modifier_mask; - - return sym; -} - -termkey_keysym termkey_register_csi_ss3(termkey_t *tk, termkey_type type, termkey_keysym sym, unsigned char cmd, const char *name) -{ - return termkey_register_csi_ss3_full(tk, type, sym, 0, 0, cmd, name); -} - -termkey_keysym termkey_register_csi_ss3_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char cmd, const char *name) -{ - if(cmd < 0x40 || cmd >= 0x80) { - fprintf(stderr, "Cannot register CSI/SS3 key at cmd 0x%02x - out of bounds\n", cmd); - return -1; - } - - if(name) - sym = termkey_register_keyname(tk, sym, name); - - tk->csi_ss3s[cmd - 0x40].type = type; - tk->csi_ss3s[cmd - 0x40].sym = sym; - tk->csi_ss3s[cmd - 0x40].modifier_set = modifier_set; - tk->csi_ss3s[cmd - 0x40].modifier_mask = modifier_mask; - - return sym; -} - -termkey_keysym termkey_register_ss3kpalt(termkey_t *tk, termkey_type type, termkey_keysym sym, unsigned char cmd, const char *name, char kpalt) -{ - return termkey_register_ss3kpalt_full(tk, type, sym, 0, 0, cmd, name, kpalt); -} - -termkey_keysym termkey_register_ss3kpalt_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, unsigned char cmd, const char *name, char kpalt) -{ - if(cmd < 0x40 || cmd >= 0x80) { - fprintf(stderr, "Cannot register SS3 key at cmd 0x%02x - out of bounds\n", cmd); - return -1; - } - - if(name) - sym = termkey_register_keyname(tk, sym, name); - - tk->ss3s[cmd - 0x40].type = type; - tk->ss3s[cmd - 0x40].sym = sym; - tk->ss3s[cmd - 0x40].modifier_set = modifier_set; - tk->ss3s[cmd - 0x40].modifier_mask = modifier_mask; - tk->ss3_kpalts[cmd - 0x40] = kpalt; - - return sym; -} - -termkey_keysym termkey_register_csifunc(termkey_t *tk, termkey_type type, termkey_keysym sym, int number, const char *name) -{ - return termkey_register_csifunc_full(tk, type, sym, 0, 0, number, name); -} - -termkey_keysym termkey_register_csifunc_full(termkey_t *tk, termkey_type type, termkey_keysym sym, int modifier_set, int modifier_mask, int number, const char *name) -{ - if(name) - sym = termkey_register_keyname(tk, sym, name); - - if(number >= tk->ncsifuncs) { - struct keyinfo *new_csifuncs = realloc(tk->csifuncs, sizeof(new_csifuncs[0]) * (number + 1)); - tk->csifuncs = new_csifuncs; - - // Fill in the hole - for(int i = tk->ncsifuncs; i < number; i++) - tk->csifuncs[i].sym = TERMKEY_SYM_UNKNOWN; - - tk->ncsifuncs = number + 1; - } - - tk->csifuncs[number].type = type; - tk->csifuncs[number].sym = sym; - tk->csifuncs[number].modifier_set = modifier_set; - tk->csifuncs[number].modifier_mask = modifier_mask; - - return sym; -} - size_t termkey_snprint_key(termkey_t *tk, char *buffer, size_t len, termkey_key *key, termkey_format format) { size_t pos = 0; |