aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--demo.c47
-rw-r--r--termkey.c438
-rw-r--r--termkey.h113
4 files changed, 615 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f0afb15
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+CCFLAGS=-Wall -Iinclude -std=c99
+LDFLAGS=
+
+CCFLAGS+=$(shell pkg-config --cflags glib-2.0)
+LDFLAGS+=$(shell pkg-config --libs glib-2.0)
+
+all: demo
+
+demo: termkey.o demo.c
+ gcc $(CCFLAGS) $(LDFLAGS) -o $@ $^
+
+termkey.o: termkey.c
+ gcc $(CCFLAGS) -o $@ -c $^
+
+.PHONY: clean
+clean:
+ rm -f *.o demo
diff --git a/demo.c b/demo.c
new file mode 100644
index 0000000..512a99f
--- /dev/null
+++ b/demo.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "termkey.h"
+
+int main(int argc, char *argv[]) {
+ struct termios termios;
+
+ if(tcgetattr(0, &termios)) {
+ perror("ioctl(TCIOGETS)");
+ exit(1);
+ }
+
+ int old_lflag = termios.c_lflag;
+ termios.c_lflag &= ~(ICANON|ECHO);
+
+ tcsetattr(0, TCSANOW, &termios);
+
+ termkey_t *tk = termkey_new(0, TERMKEY_FLAG_CONVERTKP);
+
+ termkey_key key;
+
+ while(termkey_waitkey(tk, &key) && key.code != TERMKEY_SYM_EOF) {
+ if(key.flags & TERMKEY_KEYFLAG_SPECIAL)
+ printf("Key %s%s%s%s (code %d)\n",
+ key.modifiers & TERMKEY_KEYMOD_SHIFT ? "S-" : "",
+ key.modifiers & TERMKEY_KEYMOD_ALT ? "A-" : "",
+ key.modifiers & TERMKEY_KEYMOD_CTRL ? "C-" : "",
+ termkey_describe_sym(key.code),
+ key.code);
+ else
+ printf("Key %s%s%s%s (U+%04X)\n",
+ key.modifiers & TERMKEY_KEYMOD_SHIFT ? "S-" : "",
+ key.modifiers & TERMKEY_KEYMOD_ALT ? "A-" : "",
+ key.modifiers & TERMKEY_KEYMOD_CTRL ? "C-" : "",
+ key.utf8,
+ key.code);
+
+ if(key.modifiers & TERMKEY_KEYMOD_CTRL && key.code == 'C')
+ break;
+ }
+
+ termios.c_lflag = old_lflag;
+ tcsetattr(0, TCSANOW, &termios);
+}
diff --git a/termkey.c b/termkey.c
new file mode 100644
index 0000000..ef72cbf
--- /dev/null
+++ b/termkey.c
@@ -0,0 +1,438 @@
+#include "termkey.h"
+
+#include <unistd.h>
+#include <string.h>
+
+// Use GLib to implement for now because I am lazy. Rewrite sometime
+#include <glib.h>
+#include <stdio.h>
+
+struct termkey {
+ int fd;
+ int flags;
+ unsigned char *buffer;
+ size_t buffvalid;
+ size_t buffsize;
+
+ char is_closed;
+};
+
+termkey_t *termkey_new_full(int fd, int flags, size_t buffsize)
+{
+ termkey_t *tk = g_new0(struct termkey, 1);
+
+ tk->fd = fd;
+ tk->flags = flags;
+
+ tk->buffer = g_malloc0(buffsize);
+ tk->buffvalid = 0;
+ tk->buffsize = buffsize;
+
+ tk->is_closed = 0;
+
+ return tk;
+}
+
+termkey_t *termkey_new(int fd, int flags)
+{
+ return termkey_new_full(fd, flags, 256);
+}
+
+static inline void eatbytes(termkey_t *tk, size_t count)
+{
+ memmove(tk->buffer, tk->buffer + count, tk->buffvalid - count);
+ tk->buffvalid -= count;
+}
+
+static int getkey_csi(termkey_t *tk, size_t introlen, termkey_key *key)
+{
+ size_t csi_end = introlen;
+
+ while(csi_end < tk->buffvalid) {
+ if(tk->buffer[csi_end] >= 0x40 && tk->buffer[csi_end] < 0x80)
+ break;
+ csi_end++;
+ }
+
+ if(csi_end >= tk->buffvalid) {
+ key->code = TERMKEY_SYM_NONE;
+ return key->code;
+ }
+
+ unsigned char cmd = tk->buffer[csi_end];
+ int arg1 = -1;
+ int arg2 = -1;
+
+ size_t p = introlen;
+
+ // Now attempt to parse out up to the first two 1;2; separated arguments
+ if(tk->buffer[p] >= '0' && tk->buffer[p] <= '9') {
+ arg1 = 0;
+ while(p < csi_end && tk->buffer[p] != ';') {
+ arg1 = (arg1 * 10) + tk->buffer[p] - '0';
+ p++;
+ }
+
+ if(tk->buffer[p] == ';')
+ p++;
+
+ if(tk->buffer[p] >= '0' && tk->buffer[p] <= '9') {
+ arg2 = 0;
+ while(p < csi_end && tk->buffer[p] != ';') {
+ arg2 = (arg2 * 10) + tk->buffer[p] - '0';
+ p++;
+ }
+ }
+ }
+
+ key->code = TERMKEY_SYM_UNKNOWN;
+
+ switch(cmd) {
+ case 'A': key->code = TERMKEY_SYM_UP; break;
+ case 'B': key->code = TERMKEY_SYM_DOWN; break;
+ case 'C': key->code = TERMKEY_SYM_RIGHT; break;
+ case 'D': key->code = TERMKEY_SYM_LEFT; break;
+ case 'E': key->code = TERMKEY_SYM_BEGIN; break;
+ case 'F': key->code = TERMKEY_SYM_END; break;
+ case 'H': key->code = TERMKEY_SYM_HOME; break;
+ case 'P': key->code = TERMKEY_SYM_F1; break;
+ case 'Q': key->code = TERMKEY_SYM_F2; break;
+ case 'R': key->code = TERMKEY_SYM_F3; break;
+ case 'S': key->code = TERMKEY_SYM_F4; break;
+ case '~':
+ switch(arg1) {
+ case 1: key->code = TERMKEY_SYM_HOME; break;
+ case 2: key->code = TERMKEY_SYM_INSERT; break;
+ case 3: key->code = TERMKEY_SYM_DELETE; break;
+ case 4: key->code = TERMKEY_SYM_END; break;
+ case 5: key->code = TERMKEY_SYM_PAGEUP; break;
+ case 6: key->code = TERMKEY_SYM_PAGEDOWN; break;
+ case 15: key->code = TERMKEY_SYM_F5; break;
+ case 17: key->code = TERMKEY_SYM_F6; break;
+ case 18: key->code = TERMKEY_SYM_F7; break;
+ case 19: key->code = TERMKEY_SYM_F8; break;
+ case 20: key->code = TERMKEY_SYM_F9; break;
+ case 21: key->code = TERMKEY_SYM_F10; break;
+ case 23: key->code = TERMKEY_SYM_F11; break;
+ case 24: key->code = TERMKEY_SYM_F12; break;
+
+ default:
+ fprintf(stderr, "CSI function key %d\n", arg1);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "CSI arg1=%d arg2=%d cmd=%c\n", arg1, arg2, cmd);
+ }
+
+ key->modifiers = arg2 == -1 ? 0 : arg2 - 1;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+
+ eatbytes(tk, csi_end + 1);
+
+ return key->code;
+}
+
+static int getkey_ss3(termkey_t *tk, size_t introlen, termkey_key *key)
+{
+ if(introlen + 1 < tk->buffvalid) {
+ key->code = TERMKEY_SYM_NONE;
+ return key->code;
+ }
+
+ unsigned char cmd = tk->buffer[introlen];
+
+ key->code = TERMKEY_SYM_UNKNOWN;
+
+ switch(cmd) {
+ case 'A': key->code = TERMKEY_SYM_UP; break;
+ case 'B': key->code = TERMKEY_SYM_DOWN; break;
+ case 'C': key->code = TERMKEY_SYM_RIGHT; break;
+ case 'D': key->code = TERMKEY_SYM_LEFT; break;
+ case 'F': key->code = TERMKEY_SYM_END; break;
+ case 'H': key->code = TERMKEY_SYM_HOME; break;
+ case 'M': key->code = TERMKEY_SYM_KPENTER; break;
+ case 'P': key->code = TERMKEY_SYM_F1; break;
+ case 'Q': key->code = TERMKEY_SYM_F2; break;
+ case 'R': key->code = TERMKEY_SYM_F3; break;
+ case 'S': key->code = TERMKEY_SYM_F4; break;
+ case 'X': key->code = TERMKEY_SYM_KPEQUALS; break;
+ case 'j': key->code = TERMKEY_SYM_KPMULT; break;
+ case 'k': key->code = TERMKEY_SYM_KPPLUS; break;
+ case 'l': key->code = TERMKEY_SYM_KPCOMMA; break;
+ case 'm': key->code = TERMKEY_SYM_KPMINUS; break;
+ case 'n': key->code = TERMKEY_SYM_KPPERIOD; break;
+ case 'o': key->code = TERMKEY_SYM_KPDIV; break;
+ case 'p': key->code = TERMKEY_SYM_KP0; break;
+ case 'q': key->code = TERMKEY_SYM_KP1; break;
+ case 'r': key->code = TERMKEY_SYM_KP2; break;
+ case 's': key->code = TERMKEY_SYM_KP3; break;
+ case 't': key->code = TERMKEY_SYM_KP4; break;
+ case 'u': key->code = TERMKEY_SYM_KP5; break;
+ case 'v': key->code = TERMKEY_SYM_KP6; break;
+ case 'w': key->code = TERMKEY_SYM_KP7; break;
+ case 'x': key->code = TERMKEY_SYM_KP8; break;
+ case 'y': key->code = TERMKEY_SYM_KP9; break;
+
+ default:
+ fprintf(stderr, "SS3 %c (0x%02x)\n", cmd, cmd);
+ break;
+ }
+
+ key->modifiers = 0;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+
+ if(tk->flags & TERMKEY_FLAG_CONVERTKP) {
+ char is_kp = 1;
+ switch(key->code) {
+ case TERMKEY_SYM_KP0: key->code = '0'; break;
+ case TERMKEY_SYM_KP1: key->code = '1'; break;
+ case TERMKEY_SYM_KP2: key->code = '2'; break;
+ case TERMKEY_SYM_KP3: key->code = '3'; break;
+ case TERMKEY_SYM_KP4: key->code = '4'; break;
+ case TERMKEY_SYM_KP5: key->code = '5'; break;
+ case TERMKEY_SYM_KP6: key->code = '6'; break;
+ case TERMKEY_SYM_KP7: key->code = '7'; break;
+ case TERMKEY_SYM_KP8: key->code = '8'; break;
+ case TERMKEY_SYM_KP9: key->code = '9'; break;
+ case TERMKEY_SYM_KPPLUS: key->code = '+'; break;
+ case TERMKEY_SYM_KPMINUS: key->code = '-'; break;
+ case TERMKEY_SYM_KPMULT: key->code = '*'; break;
+ case TERMKEY_SYM_KPDIV: key->code = '/'; break;
+ case TERMKEY_SYM_KPCOMMA: key->code = ','; break;
+ case TERMKEY_SYM_KPPERIOD: key->code = '.'; break;
+ case TERMKEY_SYM_KPEQUALS: key->code = '='; break;
+
+ default:
+ is_kp = 0;
+ break;
+ }
+
+ if(is_kp) {
+ key->flags = 0;
+
+ key->utf8[0] = key->code;
+ key->utf8[1] = 0;
+ }
+ }
+
+ eatbytes(tk, introlen + 1);
+
+ return key->code;
+}
+
+int termkey_getkey(termkey_t *tk, termkey_key *key)
+{
+ if(tk->buffvalid == 0) {
+ key->code = tk->is_closed ? TERMKEY_SYM_EOF : TERMKEY_SYM_NONE;
+ return key->code;
+ }
+
+ // Now we're sure at least 1 byte is valid
+ unsigned char b0 = tk->buffer[0];
+
+ if(b0 == 0x1b) {
+ if(tk->buffvalid == 1) {
+ key->code = TERMKEY_SYM_ESCAPE;
+ key->modifiers = 0;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+
+ eatbytes(tk, 1);
+
+ return key->code;
+ }
+
+ unsigned char b1 = tk->buffer[1];
+ if(b1 == '[')
+ return getkey_csi(tk, 2, key);
+
+ if(b1 == 'O')
+ return getkey_ss3(tk, 2, key);
+
+ tk->buffer++;
+
+ int metakey = termkey_getkey(tk, key);
+ if(metakey != TERMKEY_SYM_NONE) {
+ key->modifiers |= TERMKEY_KEYMOD_ALT;
+ tk->buffer--;
+ eatbytes(tk, 1);
+ }
+
+ return key->code;
+ }
+ else if(b0 < 0x20) {
+ // Control key
+
+ key->code = TERMKEY_SYM_UNKNOWN;
+
+ if(!(tk->flags & TERMKEY_FLAG_NOINTERPRET)) {
+ // Try to interpret C0 codes that have nice names
+ switch(b0) {
+ case 0x08: key->code = TERMKEY_SYM_BACKSPACE; break;
+ case 0x09: key->code = TERMKEY_SYM_TAB; break;
+ case 0x0a: key->code = TERMKEY_SYM_ENTER; break;
+ }
+ }
+
+ if(key->code == TERMKEY_SYM_UNKNOWN) {
+ key->code = b0 + 0x40;
+ key->modifiers = TERMKEY_KEYMOD_CTRL;
+ key->flags = 0;
+
+ key->utf8[0] = key->code;
+ key->utf8[1] = 0;
+ }
+ else {
+ key->modifiers = 0;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+ }
+
+ eatbytes(tk, 1);
+
+ return key->code;
+ }
+ else if(b0 == 0x20 && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) {
+ key->code = TERMKEY_SYM_SPACE;
+ key->modifiers = 0;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+
+ eatbytes(tk, 1);
+
+ return key->code;
+ }
+ else if(b0 == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) {
+ key->code = TERMKEY_SYM_DEL;
+ key->modifiers = 0;
+ key->flags = TERMKEY_KEYFLAG_SPECIAL;
+
+ eatbytes(tk, 1);
+
+ return key->code;
+ }
+ else if(b0 >= 0x20 && b0 < 0x80) {
+ // ASCII lowbyte range
+ key->code = b0;
+ key->modifiers = 0;
+ key->flags = 0;
+
+ key->utf8[0] = key->code;
+ key->utf8[1] = 0;
+
+ eatbytes(tk, 1);
+
+ return key->code;
+ }
+
+ fprintf(stderr, "TODO - tk->buffer[0] == 0x%02x\n", tk->buffer[0]);
+ return TERMKEY_SYM_NONE;
+}
+
+int termkey_waitkey(termkey_t *tk, termkey_key *key)
+{
+ int ret;
+ while((ret = termkey_getkey(tk, key)) == TERMKEY_SYM_NONE) {
+ termkey_advisereadable(tk);
+ }
+
+ return ret;
+}
+
+void termkey_pushinput(termkey_t *tk, unsigned char *input, size_t inputlen)
+{
+ if(tk->buffvalid + inputlen > tk->buffsize) {
+ fprintf(stderr, "TODO! Extend input buffer!\n");
+ exit(0);
+ }
+
+ // Not strcpy just in case of NUL bytes
+ memcpy(tk->buffer + tk->buffvalid, input, inputlen);
+ tk->buffvalid += inputlen;
+}
+
+void termkey_advisereadable(termkey_t *tk)
+{
+ unsigned char buffer[64]; // Smaller than the default size
+ size_t len = read(tk->fd, buffer, sizeof buffer);
+
+ if(len == -1)
+ tk->is_closed = 1;
+ else
+ termkey_pushinput(tk, buffer, len);
+}
+
+// Must be kept synchronised with enum termkey_sym_e in termkey.h
+const char *keynames[] = {
+ "None",
+
+ // Special names in C0
+ "Backspace",
+ "Tab",
+ "Enter",
+ "Escape",
+
+ // Special names in G0
+ "Space",
+ "DEL",
+
+ // CSI keys
+ "Up",
+ "Down",
+ "Left",
+ "Right",
+ "Begin",
+
+ // CSI function keys
+ "Insert",
+ "Delete",
+ "Home",
+ "End",
+ "PageUp",
+ "PageDown",
+
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+
+ // Numeric keypad special keys
+ "KP0",
+ "KP1",
+ "KP2",
+ "KP3",
+ "KP4",
+ "KP5",
+ "KP6",
+ "KP7",
+ "KP8",
+ "KP9",
+ "KPEnter",
+ "KPPlus",
+ "KPMinus",
+ "KPMult",
+ "KPDiv",
+ "KPComma",
+ "KPPeriod",
+ "KPEquals",
+};
+
+const char *termkey_describe_sym(int code)
+{
+ if(code == -1)
+ return "EOF";
+
+ if(code == -2)
+ return "UNKNOWN";
+
+ if(code < sizeof(keynames)/sizeof(keynames[0]))
+ return keynames[code];
+
+ return "UNKNOWN";
+}
diff --git a/termkey.h b/termkey.h
new file mode 100644
index 0000000..a99e428
--- /dev/null
+++ b/termkey.h
@@ -0,0 +1,113 @@
+#ifndef __TERMKEY_H__
+#define __TERMKEY_H__
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef enum {
+ TERMKEY_SYM_EOF = -1, // Stream closed
+ TERMKEY_SYM_UNKNOWN = -2,
+ TERMKEY_SYM_NONE = 0, // Did not find a key
+
+ // Special names in C0
+ TERMKEY_SYM_BACKSPACE,
+ TERMKEY_SYM_TAB,
+ TERMKEY_SYM_ENTER,
+ TERMKEY_SYM_ESCAPE,
+
+ // Special names in G0
+ TERMKEY_SYM_SPACE,
+ TERMKEY_SYM_DEL,
+
+ // CSI keys
+ TERMKEY_SYM_UP,
+ TERMKEY_SYM_DOWN,
+ TERMKEY_SYM_LEFT,
+ TERMKEY_SYM_RIGHT,
+ TERMKEY_SYM_BEGIN,
+
+ // CSI function keys
+ TERMKEY_SYM_INSERT,
+ TERMKEY_SYM_DELETE,
+ TERMKEY_SYM_HOME,
+ TERMKEY_SYM_END,
+ TERMKEY_SYM_PAGEUP,
+ TERMKEY_SYM_PAGEDOWN,
+
+ TERMKEY_SYM_F1,
+ TERMKEY_SYM_F2,
+ TERMKEY_SYM_F3,
+ TERMKEY_SYM_F4,
+ TERMKEY_SYM_F5,
+ TERMKEY_SYM_F6,
+ TERMKEY_SYM_F7,
+ TERMKEY_SYM_F8,
+ TERMKEY_SYM_F9,
+ TERMKEY_SYM_F10,
+ TERMKEY_SYM_F11,
+ TERMKEY_SYM_F12,
+
+ // Numeric keypad special keys
+ TERMKEY_SYM_KP0,
+ TERMKEY_SYM_KP1,
+ TERMKEY_SYM_KP2,
+ TERMKEY_SYM_KP3,
+ TERMKEY_SYM_KP4,
+ TERMKEY_SYM_KP5,
+ TERMKEY_SYM_KP6,
+ TERMKEY_SYM_KP7,
+ TERMKEY_SYM_KP8,
+ TERMKEY_SYM_KP9,
+ TERMKEY_SYM_KPENTER,
+ TERMKEY_SYM_KPPLUS,
+ TERMKEY_SYM_KPMINUS,
+ TERMKEY_SYM_KPMULT,
+ TERMKEY_SYM_KPDIV,
+ TERMKEY_SYM_KPCOMMA,
+ TERMKEY_SYM_KPPERIOD,
+ TERMKEY_SYM_KPEQUALS,
+
+ // et cetera ad nauseum
+} termkey_sym_e;
+
+enum {
+ TERMKEY_KEYFLAG_SPECIAL = 0x01, // 'code' is a special keycode, not a unicode codepoint
+};
+
+enum {
+ TERMKEY_KEYMOD_SHIFT = 0x01,
+ TERMKEY_KEYMOD_ALT = 0x02,
+ TERMKEY_KEYMOD_CTRL = 0x04,
+};
+
+typedef struct {
+ int modifiers;
+ int code;
+ int flags;
+
+ /* Any Unicode character can be UTF-8 encoded in no more than 5 bytes, plus
+ * terminating NUL */
+ char utf8[6];
+} termkey_key;
+
+typedef struct termkey termkey_t;
+
+enum {
+ TERMKEY_FLAG_NOINTERPRET = 0x01, // Do not interpret C0//G1 codes if possible
+ TERMKEY_FLAG_CONVERTKP = 0x02, // Convert KP codes to regular keypresses
+};
+
+termkey_t *termkey_new(int fd, int flags);
+void termkey_free(termkey_t *tk);
+
+int termkey_getkey(termkey_t *tk, termkey_key *key);
+int termkey_waitkey(termkey_t *tk, termkey_key *key);
+
+void termkey_pushinput(termkey_t *tk, unsigned char *input, size_t inputlen);
+
+void termkey_advisereadable(termkey_t *tk);
+
+
+const char *termkey_describe_sym(int code);
+
+#endif