3 // Copyright 2020 Bruce Hill
4 // Released under the MIT License
6 // Implementations of some basic terminal stuff, like reading keys and some
7 // terminal escape sequences.
22 static keyname_t key_names[] = {
24 {KEY_BACKSPACE2, "Backspace"},
37 {KEY_INSERT, "Insert"},
38 {KEY_DELETE, "Delete"},
42 {KEY_PGUP, "Page Up"},
44 {KEY_PGDN, "Page Down"},
46 {KEY_ARROW_DOWN, "Down"},
47 {KEY_ARROW_LEFT, "Left"},
48 {KEY_ARROW_RIGHT, "Right"},
49 {MOUSE_LEFT_PRESS, "Left press"},
50 {MOUSE_RIGHT_PRESS, "Right press"},
51 {MOUSE_MIDDLE_PRESS, "Middle press"},
52 {MOUSE_LEFT_DRAG, "Left drag"},
53 {MOUSE_RIGHT_DRAG, "Right drag"},
54 {MOUSE_MIDDLE_DRAG, "Middle drag"},
55 {MOUSE_LEFT_RELEASE, "Left click"},
56 {MOUSE_RIGHT_RELEASE, "Right click"},
57 {MOUSE_MIDDLE_RELEASE, "Middle click"},
58 {MOUSE_LEFT_RELEASE, "Left up"},
59 {MOUSE_RIGHT_RELEASE, "Right up"},
60 {MOUSE_MIDDLE_RELEASE, "Middle up"},
61 {MOUSE_LEFT_RELEASE, "Left release"},
62 {MOUSE_RIGHT_RELEASE, "Right release"},
63 {MOUSE_MIDDLE_RELEASE, "Middle release"},
64 {MOUSE_LEFT_DOUBLE, "Double left click"},
65 {MOUSE_RIGHT_DOUBLE, "Double right click"},
66 {MOUSE_MIDDLE_DOUBLE, "Double middle click"},
67 {MOUSE_WHEEL_RELEASE, "Mouse wheel up"},
68 {MOUSE_WHEEL_PRESS, "Mouse wheel down"},
71 {KEY_ENTER, "Return"},
72 {KEY_CTRL_A, "Ctrl-a"},
73 {KEY_CTRL_B, "Ctrl-b"},
74 {KEY_CTRL_C, "Ctrl-c"},
75 {KEY_CTRL_D, "Ctrl-d"},
76 {KEY_CTRL_E, "Ctrl-e"},
77 {KEY_CTRL_F, "Ctrl-f"},
78 {KEY_CTRL_G, "Ctrl-g"},
79 {KEY_CTRL_H, "Ctrl-h"},
80 {KEY_CTRL_I, "Ctrl-i"},
81 {KEY_CTRL_J, "Ctrl-j"},
82 {KEY_CTRL_K, "Ctrl-k"},
83 {KEY_CTRL_L, "Ctrl-l"},
84 {KEY_CTRL_M, "Ctrl-m"},
85 {KEY_CTRL_N, "Ctrl-n"},
86 {KEY_CTRL_O, "Ctrl-o"},
87 {KEY_CTRL_P, "Ctrl-p"},
88 {KEY_CTRL_Q, "Ctrl-q"},
89 {KEY_CTRL_R, "Ctrl-r"},
90 {KEY_CTRL_S, "Ctrl-s"},
91 {KEY_CTRL_T, "Ctrl-t"},
92 {KEY_CTRL_U, "Ctrl-u"},
93 {KEY_CTRL_V, "Ctrl-v"},
94 {KEY_CTRL_W, "Ctrl-w"},
95 {KEY_CTRL_X, "Ctrl-x"},
96 {KEY_CTRL_Y, "Ctrl-y"},
97 {KEY_CTRL_Z, "Ctrl-z"},
100 {KEY_CTRL_TILDE, "Ctrl-~"},
101 {KEY_CTRL_BACKSLASH, "Ctrl-\\"},
102 {KEY_CTRL_LSQ_BRACKET, "Ctrl-]"},
103 {KEY_CTRL_RSQ_BRACKET, "Ctrl-]"},
104 {KEY_CTRL_UNDERSCORE, "Ctrl-_"},
105 {KEY_CTRL_SLASH, "Ctrl-/"},
106 {KEY_CTRL_AT, "Ctrl-@"},
107 {KEY_CTRL_CARET, "Ctrl-^"},
108 {KEY_CTRL_BACKTICK, "Ctrl-`"},
109 {KEY_CTRL_2, "Ctrl-2"},
110 {KEY_CTRL_3, "Ctrl-3"},
111 {KEY_CTRL_4, "Ctrl-4"},
112 {KEY_CTRL_5, "Ctrl-5"},
113 {KEY_CTRL_6, "Ctrl-6"},
114 {KEY_CTRL_7, "Ctrl-7"},
115 {KEY_CTRL_5, "Ctrl-8"},
116 {KEY_CTRL_6, "Ctrl-9"},
121 static int nextchar(int fd) {
123 return read(fd, &c, 1) == 1 ? c : -1;
126 static int nextnum(int fd, int c, int *n) {
127 for (*n = 0; '0' <= c && c <= '9'; c = nextchar(fd))
128 *n = 10 * (*n) + (c - '0');
133 // Get one key of input from the given file. Returns -1 on failure.
134 // If mouse_x or mouse_y are non-null and a mouse event occurs, they will be
135 // set to the position of the mouse (0-indexed).
137 int bgetkey(FILE *in, int *mouse_x, int *mouse_y) {
138 if (mouse_x) *mouse_x = -1;
139 if (mouse_y) *mouse_y = -1;
141 int numcode = 0, modifiers = 0;
142 int c = nextchar(fd);
143 if (c == '\x1b') goto escape;
149 // Actual escape key:
150 if (c < 0) return KEY_ESC;
153 case '\x1b': return KEY_ESC;
154 case '[': c = nextchar(fd); goto CSI_start;
157 default: return MOD_ALT | c;
161 if (c == -1) return MOD_ALT | '[';
164 case 'A': return modifiers | KEY_ARROW_UP;
165 case 'B': return modifiers | KEY_ARROW_DOWN;
166 case 'C': return modifiers | KEY_ARROW_RIGHT;
167 case 'D': return modifiers | KEY_ARROW_LEFT;
168 case 'F': return modifiers | KEY_END;
169 case 'H': return modifiers | KEY_HOME;
170 case 'J': return numcode == 2 ? (MOD_SHIFT | KEY_HOME) : -1;
171 case 'K': return MOD_SHIFT | KEY_END;
172 case 'M': return MOD_CTRL | KEY_DELETE;
173 case 'P': return modifiers | (numcode == 1 ? KEY_F1 : KEY_DELETE);
174 case 'Q': return numcode == 1 ? (modifiers | KEY_F2) : -1;
175 case 'R': return numcode == 1 ? (modifiers | KEY_F3) : -1;
176 case 'S': return numcode == 1 ? (modifiers | KEY_F4) : -1;
179 case 1: return modifiers | KEY_HOME;
180 case 2: return modifiers | KEY_INSERT;
181 case 3: return modifiers | KEY_DELETE;
182 case 4: return modifiers | KEY_END;
183 case 5: return modifiers | KEY_PGUP;
184 case 6: return modifiers | KEY_PGDN;
185 case 7: return modifiers | KEY_HOME;
186 case 8: return modifiers | KEY_END;
187 case 10: return modifiers | KEY_F0;
188 case 11: return modifiers | KEY_F1;
189 case 12: return modifiers | KEY_F2;
190 case 13: return modifiers | KEY_F3;
191 case 14: return modifiers | KEY_F4;
192 case 15: return modifiers | KEY_F5;
193 case 17: return modifiers | KEY_F6;
194 case 18: return modifiers | KEY_F7;
195 case 19: return modifiers | KEY_F8;
196 case 20: return modifiers | KEY_F9;
197 case 21: return modifiers | KEY_F10;
198 case 23: return modifiers | KEY_F11;
199 case 24: return modifiers | KEY_F12;
203 case '<': { // Mouse clicks
204 int buttons = 0, x = 0, y = 0;
205 c = nextnum(fd, nextchar(fd), &buttons);
206 if (c != ';') return -1;
207 c = nextnum(fd, nextchar(fd), &x);
208 if (c != ';') return -1;
209 c = nextnum(fd, nextchar(fd), &y);
210 if (c != 'm' && c != 'M') return -1;
212 if (mouse_x) *mouse_x = x - 1;
213 if (mouse_y) *mouse_y = y - 1;
215 if (buttons & 4) modifiers |= MOD_SHIFT;
216 if (buttons & 8) modifiers |= MOD_META;
217 if (buttons & 16) modifiers |= MOD_CTRL;
219 switch (buttons & ~(4 | 8 | 16)) {
220 case 0: key = c == 'm' ? MOUSE_LEFT_RELEASE : MOUSE_LEFT_PRESS; break;
221 case 1: key = c == 'm' ? MOUSE_MIDDLE_RELEASE : MOUSE_MIDDLE_PRESS; break;
222 case 2: key = c == 'm' ? MOUSE_RIGHT_RELEASE : MOUSE_RIGHT_PRESS; break;
223 case 32: key = MOUSE_LEFT_DRAG; break;
224 case 33: key = MOUSE_MIDDLE_DRAG; break;
225 case 34: key = MOUSE_RIGHT_DRAG; break;
226 case 64: key = MOUSE_WHEEL_RELEASE; break;
227 case 65: key = MOUSE_WHEEL_PRESS; break;
230 if (key == MOUSE_LEFT_RELEASE || key == MOUSE_RIGHT_RELEASE || key == MOUSE_MIDDLE_RELEASE) {
231 static int lastclick = -1;
232 static struct timespec lastclicktime = {0, 0};
233 struct timespec clicktime;
234 clock_gettime(CLOCK_MONOTONIC, &clicktime);
235 if (key == lastclick) {
236 double dt_ms = 1e3 * (double)(clicktime.tv_sec - lastclicktime.tv_sec)
237 + 1e-6 * (double)(clicktime.tv_nsec - lastclicktime.tv_nsec);
238 if (dt_ms < DOUBLECLICK_THRESHOLD) {
240 case MOUSE_LEFT_RELEASE: key = MOUSE_LEFT_DOUBLE; break;
241 case MOUSE_RIGHT_RELEASE: key = MOUSE_RIGHT_DOUBLE; break;
242 case MOUSE_MIDDLE_RELEASE: key = MOUSE_MIDDLE_DOUBLE; break;
247 lastclicktime = clicktime;
250 return modifiers | key;
253 if ('0' <= c && c <= '9') {
255 c = nextnum(fd, c, &numcode);
257 c = nextnum(fd, nextchar(fd), &modifiers);
258 modifiers = (modifiers >> 1) << MOD_BITSHIFT;
269 switch (nextchar(fd)) {
270 case 'P': return KEY_F1;
271 case 'Q': return KEY_F2;
272 case 'R': return KEY_F3;
273 case 'S': return KEY_F4;
280 // Populate `buf` with the name of a key.
282 char *bkeyname(int key, char *buf) {
283 if (key & MOD_META) buf = stpcpy(buf, "Super-");
284 if (key & MOD_CTRL) buf = stpcpy(buf, "Ctrl-");
285 if (key & MOD_ALT) buf = stpcpy(buf, "Alt-");
286 if (key & MOD_SHIFT) buf = stpcpy(buf, "Shift-");
287 key &= ~(MOD_META | MOD_CTRL | MOD_ALT | MOD_SHIFT);
288 for (size_t i = 0; i < sizeof(key_names) / sizeof(key_names[0]); i++) {
289 if (key_names[i].key == key) {
290 return stpcpy(buf, key_names[i].name);
293 if (' ' < key && key <= '~') return buf + sprintf(buf, "%c", key);
294 else return buf + sprintf(buf, "\\x%02X", (unsigned int)key);
298 // Return the key with the given name, if one exists, otherwise -1.
299 // (i.e. bkeywithname("Space") == ' ', bkeywithname("x") == 'x', bkeywithname("F1") == KEY_F1, bkeywithname("???") ==
302 int bkeywithname(const char *name) {
304 static const struct {
307 } modnames[] = {{"Super-", MOD_META}, {"Ctrl-", MOD_CTRL}, {"Alt-", MOD_ALT}, {"Shift-", MOD_SHIFT}};
309 for (size_t i = 0; i < sizeof(key_names) / sizeof(key_names[0]); i++) {
310 if (strcmp(key_names[i].name, name) == 0) return modifiers | key_names[i].key;
312 for (size_t i = 0; i < sizeof(modnames) / sizeof(modnames[0]); i++) {
313 if (strncmp(name, modnames[i].prefix, strlen(modnames[i].prefix)) == 0) {
314 modifiers |= modnames[i].modifier;
315 name += strlen(modnames[i].prefix);
319 return strlen(name) == 1 ? name[0] : -1;
321 // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0