1 #include "terminfo.h"
2 #include "../debug.h"
3 #include "../util/macros.h"
4
5 #ifndef TERMINFO_DISABLE
6
7 #include <stdint.h>
8 #include <string.h>
9 #include "key.h"
10 #include "output.h"
11 #include "terminal.h"
12 #include "xterm.h"
13 #include "../util/ascii.h"
14 #include "../util/str-util.h"
15 #include "../util/string-view.h"
16
17 #define KEY(c, k) { \
18 .code = (c), \
19 .code_length = (sizeof(c) - 1), \
20 .key = (k) \
21 }
22
23 #define XKEYS(p, key) \
24 KEY(p, key | MOD_SHIFT), \
25 KEY(p "3", key | MOD_META), \
26 KEY(p "4", key | MOD_SHIFT | MOD_META), \
27 KEY(p "5", key | MOD_CTRL), \
28 KEY(p "6", key | MOD_SHIFT | MOD_CTRL), \
29 KEY(p "7", key | MOD_META | MOD_CTRL), \
30 KEY(p "8", key | MOD_SHIFT | MOD_META | MOD_CTRL)
31
32 static struct {
33 const char *clear;
34 const char *cup;
35 const char *el;
36 const char *setab;
37 const char *setaf;
38 const char *sgr;
39 } terminfo;
40
41 static struct TermKeyMap {
42 const char *code;
43 uint32_t code_length;
44 KeyCode key;
45 } keymap[] = {
46 KEY("kcuu1", KEY_UP),
47 KEY("kcud1", KEY_DOWN),
48 KEY("kcub1", KEY_LEFT),
49 KEY("kcuf1", KEY_RIGHT),
50 KEY("kdch1", KEY_DELETE),
51 KEY("kpp", KEY_PAGE_UP),
52 KEY("knp", KEY_PAGE_DOWN),
53 KEY("khome", KEY_HOME),
54 KEY("kend", KEY_END),
55 KEY("kich1", KEY_INSERT),
56 KEY("kcbt", MOD_SHIFT | '\t'),
57 KEY("kf1", KEY_F1),
58 KEY("kf2", KEY_F2),
59 KEY("kf3", KEY_F3),
60 KEY("kf4", KEY_F4),
61 KEY("kf5", KEY_F5),
62 KEY("kf6", KEY_F6),
63 KEY("kf7", KEY_F7),
64 KEY("kf8", KEY_F8),
65 KEY("kf9", KEY_F9),
66 KEY("kf10", KEY_F10),
67 KEY("kf11", KEY_F11),
68 KEY("kf12", KEY_F12),
69 XKEYS("kUP", KEY_UP),
70 XKEYS("kDN", KEY_DOWN),
71 XKEYS("kLFT", KEY_LEFT),
72 XKEYS("kRIT", KEY_RIGHT),
73 XKEYS("kDC", KEY_DELETE),
74 XKEYS("kPRV", KEY_PAGE_UP),
75 XKEYS("kNXT", KEY_PAGE_DOWN),
76 XKEYS("kHOM", KEY_HOME),
77 XKEYS("kEND", KEY_END),
78 };
79
80 static_assert(ARRAY_COUNT(keymap) == 23 + (9 * 7));
81
82 static size_t keymap_length = 0;
83
parse_key_from_keymap(const char * buf,size_t fill,KeyCode * key)84 static ssize_t parse_key_from_keymap(const char *buf, size_t fill, KeyCode *key)
85 {
86 bool possibly_truncated = false;
87 for (size_t i = 0; i < keymap_length; i++) {
88 const struct TermKeyMap *const km = &keymap[i];
89 const char *const keycode = km->code;
90 const size_t len = km->code_length;
91 BUG_ON(keycode == NULL);
92 BUG_ON(len == 0);
93 if (len > fill) {
94 // This might be a truncated escape sequence
95 if (
96 possibly_truncated == false
97 && memcmp(keycode, buf, fill) == 0
98 ) {
99 possibly_truncated = true;
100 }
101 continue;
102 }
103 if (memcmp(keycode, buf, len) != 0) {
104 continue;
105 }
106 *key = km->key;
107 return len;
108 }
109 return possibly_truncated ? -1 : 0;
110 }
111
112 // These are normally declared in the <curses.h> and <term.h> headers.
113 // They are not included here because of the insane number of unprefixed
114 // symbols they declare and because of previous bugs caused by using them.
115 int setupterm(const char *term, int filedes, int *errret);
116 int tigetflag(const char *capname);
117 int tigetnum(const char *capname);
118 char *tigetstr(const char *capname);
119 int tputs(const char *str, int affcnt, int (*putc_fn)(int));
120 char *tparm(const char*, long, long, long, long, long, long, long, long, long);
121 #define tparm_1(str, p1) tparm(str, p1, 0, 0, 0, 0, 0, 0, 0, 0)
122 #define tparm_2(str, p1, p2) tparm(str, p1, p2, 0, 0, 0, 0, 0, 0, 0)
123
get_terminfo_string(const char * capname)124 static char *get_terminfo_string(const char *capname)
125 {
126 char *str = tigetstr(capname);
127 if (str == (char *)-1) {
128 // Not a string cap (bug?)
129 return NULL;
130 }
131 // NULL = canceled or absent
132 return str;
133 }
134
get_terminfo_string_view(const char * capname)135 static StringView get_terminfo_string_view(const char *capname)
136 {
137 return string_view_from_cstring(get_terminfo_string(capname));
138 }
139
get_terminfo_flag(const char * capname)140 static bool get_terminfo_flag(const char *capname)
141 {
142 switch (tigetflag(capname)) {
143 case -1: // Not a boolean capability
144 case 0: // Canceled or absent
145 return false;
146 }
147 return true;
148 }
149
tputs_putc(int ch)150 static int tputs_putc(int ch)
151 {
152 term_add_byte(ch);
153 return ch;
154 }
155
tputs_control_code(StringView code)156 static void tputs_control_code(StringView code)
157 {
158 if (code.length) {
159 tputs(code.data, 1, tputs_putc);
160 }
161 }
162
tputs_clear_screen(void)163 static void tputs_clear_screen(void)
164 {
165 if (terminfo.clear) {
166 tputs(terminfo.clear, terminal.height, tputs_putc);
167 }
168 }
169
tputs_clear_to_eol(void)170 static void tputs_clear_to_eol(void)
171 {
172 if (terminfo.el) {
173 tputs(terminfo.el, 1, tputs_putc);
174 }
175 }
176
tputs_move_cursor(int x,int y)177 static void tputs_move_cursor(int x, int y)
178 {
179 if (terminfo.cup) {
180 const char *seq = tparm_2(terminfo.cup, y, x);
181 if (seq) {
182 tputs(seq, 1, tputs_putc);
183 }
184 }
185 }
186
attr_is_set(const TermColor * color,unsigned int attr)187 static bool attr_is_set(const TermColor *color, unsigned int attr)
188 {
189 if (!(color->attr & attr)) {
190 return false;
191 } else if (terminal.ncv_attributes & attr) {
192 // Terminal only allows attr when not using colors
193 return color->fg == COLOR_DEFAULT && color->bg == COLOR_DEFAULT;
194 }
195 return true;
196 }
197
tputs_set_color(const TermColor * color)198 static void tputs_set_color(const TermColor *color)
199 {
200 if (same_color(color, &obuf.color)) {
201 return;
202 }
203
204 if (terminfo.sgr) {
205 const char *attrs = tparm (
206 terminfo.sgr,
207 0, // p1 = "standout" (unused)
208 attr_is_set(color, ATTR_UNDERLINE),
209 attr_is_set(color, ATTR_REVERSE),
210 attr_is_set(color, ATTR_BLINK),
211 attr_is_set(color, ATTR_DIM),
212 attr_is_set(color, ATTR_BOLD),
213 attr_is_set(color, ATTR_INVIS),
214 0, // p8 = "protect" (unused)
215 0 // p9 = "altcharset" (unused)
216 );
217 tputs(attrs, 1, tputs_putc);
218 }
219
220 TermColor c = *color;
221 if (terminfo.setaf && c.fg >= 0) {
222 const char *seq = tparm_1(terminfo.setaf, c.fg);
223 if (seq) {
224 tputs(seq, 1, tputs_putc);
225 }
226 }
227 if (terminfo.setab && c.bg >= 0) {
228 const char *seq = tparm_1(terminfo.setab, c.bg);
229 if (seq) {
230 tputs(seq, 1, tputs_putc);
231 }
232 }
233
234 obuf.color = *color;
235 }
236
convert_ncv_flags_to_attrs(unsigned int ncv)237 static unsigned int convert_ncv_flags_to_attrs(unsigned int ncv)
238 {
239 // These flags should have values equal to their terminfo
240 // counterparts:
241 static_assert(ATTR_UNDERLINE == 2);
242 static_assert(ATTR_REVERSE == 4);
243 static_assert(ATTR_BLINK == 8);
244 static_assert(ATTR_DIM == 16);
245 static_assert(ATTR_BOLD == 32);
246 static_assert(ATTR_INVIS == 64);
247
248 // Mask flags to supported, common subset
249 unsigned int attrs = ncv & (
250 ATTR_UNDERLINE | ATTR_REVERSE | ATTR_BLINK
251 | ATTR_DIM | ATTR_BOLD | ATTR_INVIS
252 );
253
254 // Italic is a special case; it occupies bit 16 in terminfo
255 // but bit 7 here
256 if (ncv & 0x8000) {
257 attrs |= ATTR_ITALIC;
258 }
259
260 return attrs;
261 }
262
term_init_terminfo(const char * term)263 bool term_init_terminfo(const char *term)
264 {
265 // Initialize terminfo database (or call exit(3) on failure)
266 setupterm(term, 1, (int*)0);
267
268 terminal.put_control_code = &tputs_control_code;
269 terminal.clear_screen = &tputs_clear_screen;
270 terminal.clear_to_eol = &tputs_clear_to_eol;
271 terminal.set_color = &tputs_set_color;
272 terminal.move_cursor = &tputs_move_cursor;
273
274 if (get_terminfo_flag("nxon")) {
275 term_init_fail (
276 "TERM type '%s' not supported: 'nxon' flag is set",
277 term
278 );
279 }
280
281 terminfo.cup = get_terminfo_string("cup");
282 if (terminfo.cup == NULL) {
283 term_init_fail (
284 "TERM type '%s' not supported: no 'cup' capability",
285 term
286 );
287 }
288
289 terminfo.clear = get_terminfo_string("clear");
290 terminfo.el = get_terminfo_string("el");
291 terminfo.setab = get_terminfo_string("setab");
292 terminfo.setaf = get_terminfo_string("setaf");
293 terminfo.sgr = get_terminfo_string("sgr");
294
295 terminal.back_color_erase = get_terminfo_flag("bce");
296 terminal.width = tigetnum("cols");
297 terminal.height = tigetnum("lines");
298
299 switch (tigetnum("colors")) {
300 case 16777216:
301 // Just use the built-in xterm_set_color() function if true color
302 // support is indicated. This bypasses tputs(3), but no true color
303 // terminal in existence actually depends on archaic tputs(3)
304 // features (like e.g. baudrate-dependant padding).
305 terminal.color_type = TERM_TRUE_COLOR;
306 terminal.set_color = &xterm_set_color;
307 break;
308 case 256:
309 terminal.color_type = TERM_256_COLOR;
310 break;
311 case 16:
312 terminal.color_type = TERM_16_COLOR;
313 break;
314 case 88:
315 case 8:
316 terminal.color_type = TERM_8_COLOR;
317 break;
318 default:
319 terminal.color_type = TERM_0_COLOR;
320 break;
321 }
322
323 const int ncv = tigetnum("ncv");
324 if (ncv <= 0) {
325 terminal.ncv_attributes = 0;
326 } else {
327 terminal.ncv_attributes = convert_ncv_flags_to_attrs(ncv);
328 }
329
330 terminal.control_codes = (TermControlCodes) {
331 .reset_colors = get_terminfo_string_view("op"),
332 .reset_attrs = get_terminfo_string_view("sgr0"),
333 .keypad_off = get_terminfo_string_view("rmkx"),
334 .keypad_on = get_terminfo_string_view("smkx"),
335 .cup_mode_off = get_terminfo_string_view("rmcup"),
336 .cup_mode_on = get_terminfo_string_view("smcup"),
337 .show_cursor = get_terminfo_string_view("cnorm"),
338 .hide_cursor = get_terminfo_string_view("civis")
339 };
340
341 bool xterm_compatible_key_codes = true;
342
343 for (size_t i = 0; i < ARRAY_COUNT(keymap); i++) {
344 const char *const code = get_terminfo_string(keymap[i].code);
345 if (code && code[0] != '\0') {
346 const size_t code_len = strlen(code);
347 const KeyCode key = keymap[i].key;
348 keymap[keymap_length++] = (struct TermKeyMap) {
349 .code = code,
350 .code_length = code_len,
351 .key = key
352 };
353 KeyCode parsed_key;
354 const ssize_t parsed_len = xterm_parse_key(code, code_len, &parsed_key);
355 if (parsed_len <= 0 || parsed_key != key) {
356 xterm_compatible_key_codes = false;
357 }
358 }
359 }
360
361 if (!xterm_compatible_key_codes) {
362 terminal.parse_key_sequence = &parse_key_from_keymap;
363 }
364
365 return true; // Initialization succeeded
366 }
367
368 #else
369
term_init_terminfo(const char * UNUSED_ARG (term))370 bool term_init_terminfo(const char* UNUSED_ARG(term))
371 {
372 return false; // terminfo not available
373 }
374
375 #endif // ifndef TERMINFO_DISABLE
376