==> src/input.c <==1/* Key decoding after crossterm 0.29's unix parser (parse_event): the subset of sequences xterm,
2 * VTE, tmux and the Linux console send for these keys. */
3#include "tui/input.h"
4
5#include "tui/utf8.h"
6
7static tui_key mk(tui_key_code c, uint8_t mods) { return (tui_key){c, 0, 0, mods, TUI_KEY_PRESS}; }
8static tui_key fkey(uint8_t f, uint8_t mods) { return (tui_key){TUI_KEY_F, 0, f, mods, TUI_KEY_PRESS}; }
9
10/* xterm's modifier parameter: 1 + (shift 1 | alt 2 | ctrl 4). */
11static uint8_t mods_of(unsigned p) {
12 if (p < 2) return 0;
13 p--;
14 return (uint8_t)(((p & 1) ? TUI_MOD_SHIFT : 0) | ((p & 2) ? TUI_MOD_ALT : 0) | ((p & 4) ? TUI_MOD_CONTROL : 0));
15}
16
17static size_t csi(const char *in, size_t len, tui_key *k) {
18 /* in[0] == ESC, in[1] == '['. Parameters, then a final byte 0x40..0x7E. */
19 unsigned params[4] = {0, 0, 0, 0};
20 int np = 0;
21 size_t i = 2;
22 if (i < len && (in[i] == '<' || in[i] == '?' || in[i] == '>')) i++; /* private markers: consumed, ignored */
23 for (; i < len; i++) {
24 unsigned char c = (unsigned char)in[i];
25 if (c >= '0' && c <= '9') {
26 if (np == 0) np = 1;
27 if (np <= 4) params[np - 1] = params[np - 1] * 10 + (c - '0');
28 } else if (c == ';' || c == ':') {
29 if (np == 0) np = 1;
30 np++;
31 } else if (c >= 0x40 && c <= 0x7E) {
32 uint8_t m = np >= 2 ? mods_of(params[1]) : 0;
33 *k = mk(TUI_KEY_NONE, 0);
34 switch (c) {
35 case 'A': *k = mk(TUI_KEY_UP, m); break;
36 case 'B': *k = mk(TUI_KEY_DOWN, m); break;
37 case 'C': *k = mk(TUI_KEY_RIGHT, m); break;
38 case 'D': *k = mk(TUI_KEY_LEFT, m); break;
39 case 'H': *k = mk(TUI_KEY_HOME, m); break;
40 case 'F': *k = mk(TUI_KEY_END, m); break;
41 case 'Z': *k = mk(TUI_KEY_BACKTAB, TUI_MOD_SHIFT); break;
42 case 'P': *k = fkey(1, m); break;
43 case 'Q': *k = fkey(2, m); break;
44 case 'R': *k = fkey(3, m); break;
45 case 'S': *k = fkey(4, m); break;
46 case '~':
47 switch (params[0]) {
48 case 1: case 7: *k = mk(TUI_KEY_HOME, m); break;
49 case 2: *k = mk(TUI_KEY_INSERT, m); break;
50 case 3: *k = mk(TUI_KEY_DELETE, m); break;
51 case 4: case 8: *k = mk(TUI_KEY_END, m); break;
52 case 5: *k = mk(TUI_KEY_PAGE_UP, m); break;
53 case 6: *k = mk(TUI_KEY_PAGE_DOWN, m); break;
54 case 11: case 12: case 13: case 14: case 15: *k = fkey((uint8_t)(params[0] == 15 ? 5 : params[0] - 10), m); break;
55 case 17: case 18: case 19: case 20: case 21: *k = fkey((uint8_t)(params[0] - 11), m); break;
56 case 23: case 24: *k = fkey((uint8_t)(params[0] - 12), m); break;
57 default: break;
58 }
59 break;
60 default: break; /* mouse, focus, cursor reports: consumed */
61 }
62 return i + 1;
63 } else if (c < 0x20 || c > 0x7E) {
64 *k = mk(TUI_KEY_NONE, 0);
65 return i; /* malformed: drop what came before */
66 }
67 }
68 return 0; /* incomplete */
69}
70
71size_t tui_input_parse(const char *in, size_t len, bool quiet, tui_key *k) {
72 *k = mk(TUI_KEY_NONE, 0);
73 if (len == 0) return 0;
74 unsigned char b = (unsigned char)in[0];
75 if (b == 0x1B) {
76 if (len == 1) {
77 if (!quiet) return 0;
78 *k = mk(TUI_KEY_ESC, 0);
79 return 1;
80 }
81 if (in[1] == '[') return csi(in, len, k);
82 if (in[1] == 'O') {
83 if (len < 3) return quiet ? (*k = mk(TUI_KEY_ESC, 0), 1) : 0;
84 switch (in[2]) {
85 case 'A': *k = mk(TUI_KEY_UP, 0); break;
86 case 'B': *k = mk(TUI_KEY_DOWN, 0); break;
87 case 'C': *k = mk(TUI_KEY_RIGHT, 0); break;
88 case 'D': *k = mk(TUI_KEY_LEFT, 0); break;
89 case 'H': *k = mk(TUI_KEY_HOME, 0); break;
90 case 'F': *k = mk(TUI_KEY_END, 0); break;
91 case 'P': *k = fkey(1, 0); break;
92 case 'Q': *k = fkey(2, 0); break;
93 case 'R': *k = fkey(3, 0); break;
94 case 'S': *k = fkey(4, 0); break;
95 default: break;
96 }
97 return 3;
98 }
99 if (in[1] == 0x1B) {
100 *k = mk(TUI_KEY_ESC, 0);
101 return 1;
102 }
103 /* Alt + key. */
104 tui_key inner;
105 size_t n = tui_input_parse(in + 1, len - 1, quiet, &inner);
106 if (n == 0) return 0;
107 inner.mods |= TUI_MOD_ALT;
108 *k = inner;
109 return n + 1;
110 }
111 if (b == '\r' || b == '\n') {
112 *k = mk(TUI_KEY_ENTER, 0);
113 return 1;
114 }
115 if (b == '\t') {
116 *k = mk(TUI_KEY_TAB, 0);
117 return 1;
118 }
119 if (b == 0x7F || b == 0x08) {
120 *k = mk(TUI_KEY_BACKSPACE, 0);
121 return 1;
122 }
123 if (b == 0) {
124 *k = (tui_key){TUI_KEY_CHAR, ' ', 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
125 return 1;
126 }
127 if (b >= 0x01 && b <= 0x1A) {
128 *k = (tui_key){TUI_KEY_CHAR, (uint32_t)('a' + b - 1), 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
129 return 1;
130 }
131 if (b >= 0x1C && b <= 0x1F) {
132 *k = (tui_key){TUI_KEY_CHAR, (uint32_t)('4' + b - 0x1C), 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
133 return 1;
134 }
135 /* A character: wait for the rest of a UTF-8 sequence. */
136 size_t need = b < 0x80 ? 1 : b >= 0xF0 ? 4 : b >= 0xE0 ? 3 : b >= 0xC0 ? 2 : 1;
137 if (len < need) return quiet ? (*k = mk(TUI_KEY_NONE, 0), len) : 0;
138 size_t i = 0;
139 uint32_t cp = tui_utf8_next(in, need, &i);
140 *k = tui_key_char(cp);
141 if (cp < 0x80 && cp >= 'A' && cp <= 'Z') k->mods = TUI_MOD_SHIFT;
142 return i;
143}