/* * bterm.h * Copyright 2019 Bruce Hill * Released under the MIT License * * Definitions of some basic terminal stuff, like reading keys * and some terminal escape sequences. */ #ifndef FILE__BTERM_H #define FILE__BTERM_H #include #define KEY_F1 (0xFFFF-0) #define KEY_F2 (0xFFFF-1) #define KEY_F3 (0xFFFF-2) #define KEY_F4 (0xFFFF-3) #define KEY_F5 (0xFFFF-4) #define KEY_F6 (0xFFFF-5) #define KEY_F7 (0xFFFF-6) #define KEY_F8 (0xFFFF-7) #define KEY_F9 (0xFFFF-8) #define KEY_F10 (0xFFFF-9) #define KEY_F11 (0xFFFF-10) #define KEY_F12 (0xFFFF-11) #define KEY_INSERT (0xFFFF-12) #define KEY_DELETE (0xFFFF-13) #define KEY_HOME (0xFFFF-14) #define KEY_END (0xFFFF-15) #define KEY_PGUP (0xFFFF-16) #define KEY_PGDN (0xFFFF-17) #define KEY_ARROW_UP (0xFFFF-18) #define KEY_ARROW_DOWN (0xFFFF-19) #define KEY_ARROW_LEFT (0xFFFF-20) #define KEY_ARROW_RIGHT (0xFFFF-21) #define KEY_MOUSE_LEFT (0xFFFF-22) #define KEY_MOUSE_RIGHT (0xFFFF-23) #define KEY_MOUSE_MIDDLE (0xFFFF-24) #define KEY_MOUSE_RELEASE (0xFFFF-25) #define KEY_MOUSE_WHEEL_UP (0xFFFF-26) #define KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) #define KEY_MOUSE_DOUBLE_LEFT (0xFFFF-28) /* These are all ASCII code points below SPACE character and a BACKSPACE key. */ #define KEY_CTRL_TILDE 0x00 #define KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ #define KEY_CTRL_A 0x01 #define KEY_CTRL_B 0x02 #define KEY_CTRL_C 0x03 #define KEY_CTRL_D 0x04 #define KEY_CTRL_E 0x05 #define KEY_CTRL_F 0x06 #define KEY_CTRL_G 0x07 #define KEY_BACKSPACE 0x08 #define KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ #define KEY_TAB 0x09 #define KEY_CTRL_I 0x09 /* clash with 'TAB' */ #define KEY_CTRL_J 0x0A #define KEY_CTRL_K 0x0B #define KEY_CTRL_L 0x0C #define KEY_ENTER 0x0D #define KEY_CTRL_M 0x0D /* clash with 'ENTER' */ #define KEY_CTRL_N 0x0E #define KEY_CTRL_O 0x0F #define KEY_CTRL_P 0x10 #define KEY_CTRL_Q 0x11 #define KEY_CTRL_R 0x12 #define KEY_CTRL_S 0x13 #define KEY_CTRL_T 0x14 #define KEY_CTRL_U 0x15 #define KEY_CTRL_V 0x16 #define KEY_CTRL_W 0x17 #define KEY_CTRL_X 0x18 #define KEY_CTRL_Y 0x19 #define KEY_CTRL_Z 0x1A #define KEY_ESC 0x1B #define KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */ #define KEY_CTRL_3 0x1B /* clash with 'ESC' */ #define KEY_CTRL_4 0x1C #define KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */ #define KEY_CTRL_5 0x1D #define KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */ #define KEY_CTRL_6 0x1E #define KEY_CTRL_7 0x1F #define KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */ #define KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */ #define KEY_SPACE 0x20 #define KEY_BACKSPACE2 0x7F #define KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */ // Terminal escape sequences: #define CSI "\033[" #define T_WRAP "7" #define T_SHOW_CURSOR "25" #define T_MOUSE_XY "1000" #define T_MOUSE_CELL "1002" #define T_MOUSE_SGR "1006" #define T_ALT_SCREEN "1049" #define T_ON(opt) CSI "?" opt "h" #define T_OFF(opt) CSI "?" opt "l" #define move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1) typedef struct { int key; const char *name; } keyname_t; static keyname_t key_names[] = { {KEY_F1, "F1"}, {KEY_F2, "F2"}, {KEY_F3, "F3"}, {KEY_F4, "F4"}, {KEY_F5, "F5"}, {KEY_F6, "F6"}, {KEY_F7, "F7"}, {KEY_F8, "F8"}, {KEY_F9, "F9"}, {KEY_F10, "F10"}, {KEY_F11, "F11"}, {KEY_F12, "F12"}, {KEY_INSERT, "Insert"}, {KEY_DELETE, "Delete"}, {KEY_HOME, "Home"}, {KEY_END, "End"}, {KEY_PGUP, "PgUp"}, {KEY_PGUP, "Page Up"}, {KEY_PGDN, "PgDn"}, {KEY_PGDN, "Page Down"}, {KEY_ARROW_UP, "Up"}, {KEY_ARROW_DOWN, "Down"}, {KEY_ARROW_LEFT, "Left"}, {KEY_ARROW_RIGHT, "Right"}, {KEY_MOUSE_LEFT, "Left click"}, {KEY_MOUSE_RIGHT, "Right click"}, {KEY_MOUSE_MIDDLE, "Middle click"}, {KEY_MOUSE_RELEASE, "Mouse release"}, {KEY_MOUSE_WHEEL_UP, "Mouse wheel up"}, {KEY_MOUSE_WHEEL_DOWN, "Mouse wheel down"}, {KEY_MOUSE_DOUBLE_LEFT, "Double left click"}, {KEY_CTRL_A, "Ctrl-a"}, {KEY_CTRL_B, "Ctrl-b"}, {KEY_CTRL_C, "Ctrl-c"}, {KEY_CTRL_D, "Ctrl-d"}, {KEY_CTRL_E, "Ctrl-e"}, {KEY_CTRL_F, "Ctrl-f"}, {KEY_CTRL_G, "Ctrl-g"}, {KEY_CTRL_H, "Ctrl-h"}, {KEY_TAB, "Tab"}, {KEY_CTRL_J, "Ctrl-j"}, {KEY_CTRL_K, "Ctrl-k"}, {KEY_CTRL_L, "Ctrl-l"}, {KEY_ENTER, "Enter"}, {KEY_CTRL_N, "Ctrl-n"}, {KEY_CTRL_O, "Ctrl-o"}, {KEY_CTRL_P, "Ctrl-p"}, {KEY_CTRL_Q, "Ctrl-q"}, {KEY_CTRL_R, "Ctrl-r"}, {KEY_CTRL_S, "Ctrl-s"}, {KEY_CTRL_T, "Ctrl-t"}, {KEY_CTRL_U, "Ctrl-u"}, {KEY_CTRL_V, "Ctrl-v"}, {KEY_CTRL_W, "Ctrl-w"}, {KEY_CTRL_X, "Ctrl-x"}, {KEY_CTRL_Y, "Ctrl-y"}, {KEY_CTRL_Z, "Ctrl-z"}, {KEY_ESC, "Esc"}, {KEY_ESC, "Escape"}, {KEY_CTRL_TILDE, "Ctrl-~"}, {KEY_CTRL_TILDE, "Ctrl-2"}, {KEY_CTRL_BACKSLASH, "Ctrl-\\"}, {KEY_CTRL_BACKSLASH, "Ctrl-4"}, {KEY_CTRL_RSQ_BRACKET, "Ctrl-]"}, {KEY_CTRL_RSQ_BRACKET, "Ctrl-5"}, {KEY_CTRL_6, "Ctrl-6"}, {KEY_CTRL_SLASH, "Ctrl-_"}, {KEY_CTRL_SLASH, "Ctrl-/"}, {KEY_CTRL_SLASH, "Ctrl-7"}, {KEY_SPACE, "Space"}, {KEY_BACKSPACE2, "Backspace"}, }; int bgetkey(FILE *in, int *mouse_x, int *mouse_y); const char *bkeyname(int key); int bkeywithname(const char *name); static inline int nextchar(int fd) { char c; return read(fd, &c, 1) == 1 ? c : -1; } /* * Get one key of input from the given file. Returns -1 on failure. * If mouse_x or mouse_y are non-null and a mouse event occurs, they will be * set to the position of the mouse (0-indexed). */ int bgetkey(FILE *in, int *mouse_x, int *mouse_y) { int fd = fileno(in); int numcode = 0, super = 0; int c = nextchar(fd); if (c == '\x1b') goto escape; return c; escape: c = nextchar(fd); // Actual escape key: if (c < 0) return KEY_ESC; switch (c) { case '\x1b': ++super; goto escape; case '[': goto CSI_start; case 'P': goto DCS; case 'O': goto SS3; default: return -1; } CSI_start: c = nextchar(fd); if (c == -1) return -1; switch (c) { case 'A': return KEY_ARROW_UP; case 'B': return KEY_ARROW_DOWN; case 'C': return KEY_ARROW_RIGHT; case 'D': return KEY_ARROW_LEFT; case 'F': return KEY_END; case 'H': return KEY_HOME; case 'P': return KEY_DELETE; case '~': switch (numcode) { case 1: return KEY_HOME; case 2: return KEY_INSERT; case 3: return KEY_DELETE; case 4: return KEY_END; case 5: return KEY_PGUP; case 6: return KEY_PGDN; case 15: return KEY_F5; case 17: return KEY_F6; case 18: return KEY_F7; case 19: return KEY_F8; case 20: return KEY_F9; case 21: return KEY_F10; case 23: return KEY_F11; case 24: return KEY_F12; } return -1; case '<': { // Mouse clicks int buttons = 0, x = 0, y = 0; char buf; while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') buttons = buttons * 10 + (buf - '0'); if (buf != ';') return -1; while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') x = x * 10 + (buf - '0'); if (buf != ';') return -1; while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') y = y * 10 + (buf - '0'); if (buf != 'm' && buf != 'M') return -1; if (mouse_x) *mouse_x = x - 1; if (mouse_y) *mouse_y = y - 1; if (buf == 'm') return KEY_MOUSE_RELEASE; switch (buttons) { case 64: return KEY_MOUSE_WHEEL_UP; case 65: return KEY_MOUSE_WHEEL_DOWN; case 0: return KEY_MOUSE_LEFT; case 1: return KEY_MOUSE_RIGHT; case 2: return KEY_MOUSE_MIDDLE; default: return -1; } break; } default: if ('0' <= c && c <= '9') { // Ps prefix numcode = 10*numcode + (c - '0'); goto CSI_start; } } return -1; DCS: return -1; SS3: switch (nextchar(fd)) { case 'P': return KEY_F1; case 'Q': return KEY_F2; case 'R': return KEY_F3; case 'S': return KEY_F4; default: break; } return -1; } /* * Return the name of a key, if one exists and is different from the key itself * (i.e. bkeyname('c') == NULL, bkeyname(' ') == "Space") */ const char *bkeyname(int key) { for (int i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) { if (key_names[i].key == key) return key_names[i].name; } return NULL; } /* * Return the key with the given name, if one exists, otherwise -1. * (i.e. bkeyname("Space") == ' ', bkeyname("x") == -1, bkeyname("???") == -1) */ int bkeywithname(const char *name) { for (int i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) { if (strcmp(key_names[i].name, name) == 0) return key_names[i].key; } return -1; } #endif // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1