302 lines
9.7 KiB
C
302 lines
9.7 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
|
|
#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
|