0 /*
1 MIT License
2
3 Copyright (c) 2010-2020 nsf <no.smile.face@gmail.com>
4 2015-2022 Adam Saponara <as@php.net>
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 /* this file was edited to respect the C89 standard */
25
26 #ifndef __linux__
27 #undef _POSIX_C_SOURCE
28 #include <signal.h>
29 #endif
30
31 #ifdef sun
32 #include <stropts.h>
33 #endif
34
35 #ifndef _XOPEN_SOURCE
36 #define _XOPEN_SOURCE
37 #endif
38
39 #ifndef _DEFAULT_SOURCE
40 #define _DEFAULT_SOURCE
41 #endif
42
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <limits.h>
46 #ifndef __OpenBSD__
47 #include <signal.h>
48 #endif
49 #include <stdarg.h>
50 #include <stdint.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sys/ioctl.h>
55 #include <sys/select.h>
56 #include <sys/stat.h>
57 #include <sys/time.h>
58 #include <sys/types.h>
59 #include <termios.h>
60 #include <unistd.h>
61 #include <wchar.h>
62 #include "termbox.h"
63
64 #ifndef IMAXBEL
65 #define IMAXBEL 0
66 #endif
67
68 void cfmakeraw(struct termios *t) {
69 t->c_iflag &= ~(IMAXBEL|IGNBRK|BRKINT|PARMRK|
70 ISTRIP|INLCR|IGNCR|ICRNL|IXON);
71 t->c_oflag &= ~OPOST;
72 t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
73 t->c_cflag &= ~(CSIZE|PARENB);
74 t->c_cflag |= CS8;
75 }
76
77 #define index_fail ((size_t)-1)
78
79 #define if_err_return(rv, expr) \
80 if (((rv) = (expr)) != TB_OK) \
81 return (rv)
82 #define if_err_break(rv, expr) \
83 if (((rv) = (expr)) != TB_OK) \
84 break
85 #define if_ok_return(rv, expr) \
86 if (((rv) = (expr)) == TB_OK) \
87 return (rv)
88 #define if_ok_or_need_more_return(rv, expr) \
89 if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) \
90 return (rv)
91
92 #define send_literal(rv, a) \
93 if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1))
94
95 #define send_num(rv, nbuf, n) \
96 if_err_return((rv), bytebuf_nputs(&global.out, (nbuf), \
97 convert_num((n), (nbuf))))
98
99
100 int snprintf_(char* str, size_t sz, const char* fmt, ...) {
101 va_list args;
102 int rv;
103
104 va_start(args, fmt);
105 rv = vsnprintf(str, sz, fmt, args);
106 va_end(args);
107
108 if ((rv) < 0 || (rv) >= (int)(sz))
109 return TB_ERR;
110 return TB_OK;
111 }
112
113 #define if_not_init_return() if (!global.initialized) return TB_ERR_NOT_INIT
114
115 struct bytebuf_t {
116 char *buf;
117 size_t len;
118 size_t cap;
119 };
120
121 struct cellbuf_t {
122 int width;
123 int height;
124 struct tb_cell *cells;
125 };
126
127 struct cap_trie_t {
128 char c;
129 struct cap_trie_t *children;
130 size_t nchildren;
131 int is_leaf;
132 uint16_t key;
133 uint8_t mod;
134 };
135
136 struct tb_global_t {
137 int ttyfd;
138 int rfd;
139 int wfd;
140 int ttyfd_open;
141 int resize_pipefd[2];
142 int width;
143 int height;
144 int cursor_x;
145 int cursor_y;
146 int last_x;
147 int last_y;
148 uintattr_t fg;
149 uintattr_t bg;
150 uintattr_t last_fg;
151 uintattr_t last_bg;
152 int input_mode;
153 int output_mode;
154 char *terminfo;
155 size_t nterminfo;
156 const char *caps[TB_CAP__COUNT];
157 struct cap_trie_t cap_trie;
158 struct bytebuf_t in;
159 struct bytebuf_t out;
160 struct cellbuf_t back;
161 struct cellbuf_t front;
162 struct termios orig_tios;
163 int has_orig_tios;
164 int last_errno;
165 int initialized;
166 int (*fn_extract_esc_pre)(struct tb_event *, size_t *);
167 int (*fn_extract_esc_post)(struct tb_event *, size_t *);
168 char errbuf[1024];
169 };
170
171 static struct tb_global_t global = {0};
172
173 /* BEGIN codegen c */
174 /* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:03 +0000 */
175
176 static const int16_t terminfo_cap_indexes[] = {
177 66, /* kf1 (TB_CAP_F1) */
178 68, /* kf2 (TB_CAP_F2) */
179 69, /* kf3 (TB_CAP_F3) */
180 70, /* kf4 (TB_CAP_F4) */
181 71, /* kf5 (TB_CAP_F5) */
182 72, /* kf6 (TB_CAP_F6) */
183 73, /* kf7 (TB_CAP_F7) */
184 74, /* kf8 (TB_CAP_F8) */
185 75, /* kf9 (TB_CAP_F9) */
186 67, /* kf10 (TB_CAP_F10) */
187 216, /* kf11 (TB_CAP_F11) */
188 217, /* kf12 (TB_CAP_F12) */
189 77, /* kich1 (TB_CAP_INSERT) */
190 59, /* kdch1 (TB_CAP_DELETE) */
191 76, /* khome (TB_CAP_HOME) */
192 164, /* kend (TB_CAP_END) */
193 82, /* kpp (TB_CAP_PGUP) */
194 81, /* knp (TB_CAP_PGDN) */
195 87, /* kcuu1 (TB_CAP_ARROW_UP) */
196 61, /* kcud1 (TB_CAP_ARROW_DOWN) */
197 79, /* kcub1 (TB_CAP_ARROW_LEFT) */
198 83, /* kcuf1 (TB_CAP_ARROW_RIGHT) */
199 148, /* kcbt (TB_CAP_BACK_TAB) */
200 28, /* smcup (TB_CAP_ENTER_CA) */
201 40, /* rmcup (TB_CAP_EXIT_CA) */
202 16, /* cnorm (TB_CAP_SHOW_CURSOR) */
203 13, /* civis (TB_CAP_HIDE_CURSOR) */
204 5, /* clear (TB_CAP_CLEAR_SCREEN) */
205 39, /* sgr0 (TB_CAP_SGR0) */
206 36, /* smul (TB_CAP_UNDERLINE) */
207 27, /* bold (TB_CAP_BOLD) */
208 26, /* blink (TB_CAP_BLINK) */
209 311, /* sitm (TB_CAP_ITALIC) */
210 34, /* rev (TB_CAP_REVERSE) */
211 89, /* smkx (TB_CAP_ENTER_KEYPAD) */
212 88, /* rmkx (TB_CAP_EXIT_KEYPAD) */
213 };
214
215 /* xterm */
216 static const char *xterm_caps[] = {
217 "\033OP", /* kf1 (TB_CAP_F1) */
218 "\033OQ", /* kf2 (TB_CAP_F2) */
219 "\033OR", /* kf3 (TB_CAP_F3) */
220 "\033OS", /* kf4 (TB_CAP_F4) */
221 "\033[15~", /* kf5 (TB_CAP_F5) */
222 "\033[17~", /* kf6 (TB_CAP_F6) */
223 "\033[18~", /* kf7 (TB_CAP_F7) */
224 "\033[19~", /* kf8 (TB_CAP_F8) */
225 "\033[20~", /* kf9 (TB_CAP_F9) */
226 "\033[21~", /* kf10 (TB_CAP_F10) */
227 "\033[23~", /* kf11 (TB_CAP_F11) */
228 "\033[24~", /* kf12 (TB_CAP_F12) */
229 "\033[2~", /* kich1 (TB_CAP_INSERT) */
230 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
231 "\033OH", /* khome (TB_CAP_HOME) */
232 "\033OF", /* kend (TB_CAP_END) */
233 "\033[5~", /* kpp (TB_CAP_PGUP) */
234 "\033[6~", /* knp (TB_CAP_PGDN) */
235 "\033OA", /* kcuu1 (TB_CAP_ARROW_UP) */
236 "\033OB", /* kcud1 (TB_CAP_ARROW_DOWN) */
237 "\033OD", /* kcub1 (TB_CAP_ARROW_LEFT) */
238 "\033OC", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
239 "\033[Z", /* kcbt (TB_CAP_BACK_TAB) */
240 "\033[?1049h\033[22;0;0t", /* smcup (TB_CAP_ENTER_CA) */
241 "\033[?1049l\033[23;0;0t", /* rmcup (TB_CAP_EXIT_CA) */
242 "\033[?12l\033[?25h", /* cnorm (TB_CAP_SHOW_CURSOR) */
243 "\033[?25l", /* civis (TB_CAP_HIDE_CURSOR) */
244 "\033[H\033[2J", /* clear (TB_CAP_CLEAR_SCREEN) */
245 "\033(B\033[m", /* sgr0 (TB_CAP_SGR0) */
246 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
247 "\033[1m", /* bold (TB_CAP_BOLD) */
248 "\033[5m", /* blink (TB_CAP_BLINK) */
249 "\033[3m", /* sitm (TB_CAP_ITALIC) */
250 "\033[7m", /* rev (TB_CAP_REVERSE) */
251 "\033[?1h\033=", /* smkx (TB_CAP_ENTER_KEYPAD) */
252 "\033[?1l\033>", /* rmkx (TB_CAP_EXIT_KEYPAD) */
253 };
254
255 /* linux */
256 static const char *linux_caps[] = {
257 "\033[[A", /* kf1 (TB_CAP_F1) */
258 "\033[[B", /* kf2 (TB_CAP_F2) */
259 "\033[[C", /* kf3 (TB_CAP_F3) */
260 "\033[[D", /* kf4 (TB_CAP_F4) */
261 "\033[[E", /* kf5 (TB_CAP_F5) */
262 "\033[17~", /* kf6 (TB_CAP_F6) */
263 "\033[18~", /* kf7 (TB_CAP_F7) */
264 "\033[19~", /* kf8 (TB_CAP_F8) */
265 "\033[20~", /* kf9 (TB_CAP_F9) */
266 "\033[21~", /* kf10 (TB_CAP_F10) */
267 "\033[23~", /* kf11 (TB_CAP_F11) */
268 "\033[24~", /* kf12 (TB_CAP_F12) */
269 "\033[2~", /* kich1 (TB_CAP_INSERT) */
270 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
271 "\033[1~", /* khome (TB_CAP_HOME) */
272 "\033[4~", /* kend (TB_CAP_END) */
273 "\033[5~", /* kpp (TB_CAP_PGUP) */
274 "\033[6~", /* knp (TB_CAP_PGDN) */
275 "\033[A", /* kcuu1 (TB_CAP_ARROW_UP) */
276 "\033[B", /* kcud1 (TB_CAP_ARROW_DOWN) */
277 "\033[D", /* kcub1 (TB_CAP_ARROW_LEFT) */
278 "\033[C", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
279 "\033[Z", /* kcbt (TB_CAP_BACK_TAB) */
280 "", /* smcup (TB_CAP_ENTER_CA) */
281 "", /* rmcup (TB_CAP_EXIT_CA) */
282 "\033[?25h\033[?0c", /* cnorm (TB_CAP_SHOW_CURSOR) */
283 "\033[?25l\033[?1c", /* civis (TB_CAP_HIDE_CURSOR) */
284 "\033[H\033[J", /* clear (TB_CAP_CLEAR_SCREEN) */
285 "\033[m\017", /* sgr0 (TB_CAP_SGR0) */
286 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
287 "\033[1m", /* bold (TB_CAP_BOLD) */
288 "\033[5m", /* blink (TB_CAP_BLINK) */
289 "", /* sitm (TB_CAP_ITALIC) */
290 "\033[7m", /* rev (TB_CAP_REVERSE) */
291 "", /* smkx (TB_CAP_ENTER_KEYPAD) */
292 "", /* rmkx (TB_CAP_EXIT_KEYPAD) */
293 };
294
295 /* screen */
296 static const char *screen_caps[] = {
297 "\033OP", /* kf1 (TB_CAP_F1) */
298 "\033OQ", /* kf2 (TB_CAP_F2) */
299 "\033OR", /* kf3 (TB_CAP_F3) */
300 "\033OS", /* kf4 (TB_CAP_F4) */
301 "\033[15~", /* kf5 (TB_CAP_F5) */
302 "\033[17~", /* kf6 (TB_CAP_F6) */
303 "\033[18~", /* kf7 (TB_CAP_F7) */
304 "\033[19~", /* kf8 (TB_CAP_F8) */
305 "\033[20~", /* kf9 (TB_CAP_F9) */
306 "\033[21~", /* kf10 (TB_CAP_F10) */
307 "\033[23~", /* kf11 (TB_CAP_F11) */
308 "\033[24~", /* kf12 (TB_CAP_F12) */
309 "\033[2~", /* kich1 (TB_CAP_INSERT) */
310 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
311 "\033[1~", /* khome (TB_CAP_HOME) */
312 "\033[4~", /* kend (TB_CAP_END) */
313 "\033[5~", /* kpp (TB_CAP_PGUP) */
314 "\033[6~", /* knp (TB_CAP_PGDN) */
315 "\033OA", /* kcuu1 (TB_CAP_ARROW_UP) */
316 "\033OB", /* kcud1 (TB_CAP_ARROW_DOWN) */
317 "\033OD", /* kcub1 (TB_CAP_ARROW_LEFT) */
318 "\033OC", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
319 "\033[Z", /* kcbt (TB_CAP_BACK_TAB) */
320 "\033[?1049h", /* smcup (TB_CAP_ENTER_CA) */
321 "\033[?1049l", /* rmcup (TB_CAP_EXIT_CA) */
322 "\033[34h\033[?25h", /* cnorm (TB_CAP_SHOW_CURSOR) */
323 "\033[?25l", /* civis (TB_CAP_HIDE_CURSOR) */
324 "\033[H\033[J", /* clear (TB_CAP_CLEAR_SCREEN) */
325 "\033[m\017", /* sgr0 (TB_CAP_SGR0) */
326 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
327 "\033[1m", /* bold (TB_CAP_BOLD) */
328 "\033[5m", /* blink (TB_CAP_BLINK) */
329 "", /* sitm (TB_CAP_ITALIC) */
330 "\033[7m", /* rev (TB_CAP_REVERSE) */
331 "\033[?1h\033=", /* smkx (TB_CAP_ENTER_KEYPAD) */
332 "\033[?1l\033>", /* rmkx (TB_CAP_EXIT_KEYPAD) */
333 };
334
335 /* rxvt-256color */
336 static const char *rxvt_256color_caps[] = {
337 "\033[11~", /* kf1 (TB_CAP_F1) */
338 "\033[12~", /* kf2 (TB_CAP_F2) */
339 "\033[13~", /* kf3 (TB_CAP_F3) */
340 "\033[14~", /* kf4 (TB_CAP_F4) */
341 "\033[15~", /* kf5 (TB_CAP_F5) */
342 "\033[17~", /* kf6 (TB_CAP_F6) */
343 "\033[18~", /* kf7 (TB_CAP_F7) */
344 "\033[19~", /* kf8 (TB_CAP_F8) */
345 "\033[20~", /* kf9 (TB_CAP_F9) */
346 "\033[21~", /* kf10 (TB_CAP_F10) */
347 "\033[23~", /* kf11 (TB_CAP_F11) */
348 "\033[24~", /* kf12 (TB_CAP_F12) */
349 "\033[2~", /* kich1 (TB_CAP_INSERT) */
350 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
351 "\033[7~", /* khome (TB_CAP_HOME) */
352 "\033[8~", /* kend (TB_CAP_END) */
353 "\033[5~", /* kpp (TB_CAP_PGUP) */
354 "\033[6~", /* knp (TB_CAP_PGDN) */
355 "\033[A", /* kcuu1 (TB_CAP_ARROW_UP) */
356 "\033[B", /* kcud1 (TB_CAP_ARROW_DOWN) */
357 "\033[D", /* kcub1 (TB_CAP_ARROW_LEFT) */
358 "\033[C", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
359 "\033[Z", /* kcbt (TB_CAP_BACK_TAB) */
360 "\0337\033[?47h", /* smcup (TB_CAP_ENTER_CA) */
361 "\033[2J\033[?47l\0338", /* rmcup (TB_CAP_EXIT_CA) */
362 "\033[?25h", /* cnorm (TB_CAP_SHOW_CURSOR) */
363 "\033[?25l", /* civis (TB_CAP_HIDE_CURSOR) */
364 "\033[H\033[2J", /* clear (TB_CAP_CLEAR_SCREEN) */
365 "\033[m\017", /* sgr0 (TB_CAP_SGR0) */
366 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
367 "\033[1m", /* bold (TB_CAP_BOLD) */
368 "\033[5m", /* blink (TB_CAP_BLINK) */
369 "", /* sitm (TB_CAP_ITALIC) */
370 "\033[7m", /* rev (TB_CAP_REVERSE) */
371 "\033=", /* smkx (TB_CAP_ENTER_KEYPAD) */
372 "\033>", /* rmkx (TB_CAP_EXIT_KEYPAD) */
373 };
374
375 /* rxvt-unicode */
376 static const char *rxvt_unicode_caps[] = {
377 "\033[11~", /* kf1 (TB_CAP_F1) */
378 "\033[12~", /* kf2 (TB_CAP_F2) */
379 "\033[13~", /* kf3 (TB_CAP_F3) */
380 "\033[14~", /* kf4 (TB_CAP_F4) */
381 "\033[15~", /* kf5 (TB_CAP_F5) */
382 "\033[17~", /* kf6 (TB_CAP_F6) */
383 "\033[18~", /* kf7 (TB_CAP_F7) */
384 "\033[19~", /* kf8 (TB_CAP_F8) */
385 "\033[20~", /* kf9 (TB_CAP_F9) */
386 "\033[21~", /* kf10 (TB_CAP_F10) */
387 "\033[23~", /* kf11 (TB_CAP_F11) */
388 "\033[24~", /* kf12 (TB_CAP_F12) */
389 "\033[2~", /* kich1 (TB_CAP_INSERT) */
390 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
391 "\033[7~", /* khome (TB_CAP_HOME) */
392 "\033[8~", /* kend (TB_CAP_END) */
393 "\033[5~", /* kpp (TB_CAP_PGUP) */
394 "\033[6~", /* knp (TB_CAP_PGDN) */
395 "\033[A", /* kcuu1 (TB_CAP_ARROW_UP) */
396 "\033[B", /* kcud1 (TB_CAP_ARROW_DOWN) */
397 "\033[D", /* kcub1 (TB_CAP_ARROW_LEFT) */
398 "\033[C", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
399 "\033[Z", /* kcbt (TB_CAP_BACK_TAB) */
400 "\033[?1049h", /* smcup (TB_CAP_ENTER_CA) */
401 "\033[r\033[?1049l", /* rmcup (TB_CAP_EXIT_CA) */
402 "\033[?12l\033[?25h", /* cnorm (TB_CAP_SHOW_CURSOR) */
403 "\033[?25l", /* civis (TB_CAP_HIDE_CURSOR) */
404 "\033[H\033[2J", /* clear (TB_CAP_CLEAR_SCREEN) */
405 "\033[m\033(B", /* sgr0 (TB_CAP_SGR0) */
406 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
407 "\033[1m", /* bold (TB_CAP_BOLD) */
408 "\033[5m", /* blink (TB_CAP_BLINK) */
409 "\033[3m", /* sitm (TB_CAP_ITALIC) */
410 "\033[7m", /* rev (TB_CAP_REVERSE) */
411 "\033=", /* smkx (TB_CAP_ENTER_KEYPAD) */
412 "\033>", /* rmkx (TB_CAP_EXIT_KEYPAD) */
413 };
414
415 /* Eterm */
416 static const char *eterm_caps[] = {
417 "\033[11~", /* kf1 (TB_CAP_F1) */
418 "\033[12~", /* kf2 (TB_CAP_F2) */
419 "\033[13~", /* kf3 (TB_CAP_F3) */
420 "\033[14~", /* kf4 (TB_CAP_F4) */
421 "\033[15~", /* kf5 (TB_CAP_F5) */
422 "\033[17~", /* kf6 (TB_CAP_F6) */
423 "\033[18~", /* kf7 (TB_CAP_F7) */
424 "\033[19~", /* kf8 (TB_CAP_F8) */
425 "\033[20~", /* kf9 (TB_CAP_F9) */
426 "\033[21~", /* kf10 (TB_CAP_F10) */
427 "\033[23~", /* kf11 (TB_CAP_F11) */
428 "\033[24~", /* kf12 (TB_CAP_F12) */
429 "\033[2~", /* kich1 (TB_CAP_INSERT) */
430 "\033[3~", /* kdch1 (TB_CAP_DELETE) */
431 "\033[7~", /* khome (TB_CAP_HOME) */
432 "\033[8~", /* kend (TB_CAP_END) */
433 "\033[5~", /* kpp (TB_CAP_PGUP) */
434 "\033[6~", /* knp (TB_CAP_PGDN) */
435 "\033[A", /* kcuu1 (TB_CAP_ARROW_UP) */
436 "\033[B", /* kcud1 (TB_CAP_ARROW_DOWN) */
437 "\033[D", /* kcub1 (TB_CAP_ARROW_LEFT) */
438 "\033[C", /* kcuf1 (TB_CAP_ARROW_RIGHT) */
439 "", /* kcbt (TB_CAP_BACK_TAB) */
440 "\0337\033[?47h", /* smcup (TB_CAP_ENTER_CA) */
441 "\033[2J\033[?47l\0338", /* rmcup (TB_CAP_EXIT_CA) */
442 "\033[?25h", /* cnorm (TB_CAP_SHOW_CURSOR) */
443 "\033[?25l", /* civis (TB_CAP_HIDE_CURSOR) */
444 "\033[H\033[2J", /* clear (TB_CAP_CLEAR_SCREEN) */
445 "\033[m\017", /* sgr0 (TB_CAP_SGR0) */
446 "\033[4m", /* smul (TB_CAP_UNDERLINE) */
447 "\033[1m", /* bold (TB_CAP_BOLD) */
448 "\033[5m", /* blink (TB_CAP_BLINK) */
449 "", /* sitm (TB_CAP_ITALIC) */
450 "\033[7m", /* rev (TB_CAP_REVERSE) */
451 "", /* smkx (TB_CAP_ENTER_KEYPAD) */
452 "", /* rmkx (TB_CAP_EXIT_KEYPAD) */
453 };
454
455 static struct {
456 const char *name;
457 const char **caps;
458 const char *alias;
459 } builtin_terms[] = {
460 {"xterm", xterm_caps, "" },
461 {"linux", linux_caps, "" },
462 {"screen", screen_caps, "tmux"},
463 {"rxvt-256color", rxvt_256color_caps, "" },
464 {"rxvt-unicode", rxvt_unicode_caps, "rxvt"},
465 {"Eterm", eterm_caps, "" },
466 {NULL, NULL, NULL },
467 };
468
469 /* END codegen c */
470 #define TB_MOD_ALL TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT
471
472 static struct {
473 const char *cap;
474 const uint16_t key;
475 const uint8_t mod;
476 } builtin_mod_caps[] = {
477 /* xterm arrows */
478 {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT },
479 {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT },
480 {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT },
481 {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL },
482 {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT },
483 {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT },
484 {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_ALL },
485
486 {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT },
487 {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT },
488 {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT },
489 {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL },
490 {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT },
491 {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT },
492 {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_ALL },
493
494 {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT },
495 {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT },
496 {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT },
497 {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL },
498 {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT },
499 {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT },
500 {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_ALL },
501
502 {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT },
503 {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT },
504 {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT },
505 {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL },
506 {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT },
507 {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT },
508 {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_ALL },
509
510 /* xterm keys */
511 {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT },
512 {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT },
513 {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT },
514 {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL },
515 {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT },
516 {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT },
517 {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_ALL },
518
519 {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT },
520 {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT },
521 {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT },
522 {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL },
523 {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT },
524 {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT },
525 {"\x1b[1;8F", TB_KEY_END, TB_MOD_ALL },
526
527 {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT },
528 {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT },
529 {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT },
530 {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL },
531 {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT },
532 {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT },
533 {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_ALL },
534
535 {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT },
536 {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT },
537 {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT },
538 {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL },
539 {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT },
540 {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT },
541 {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_ALL },
542
543 {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT },
544 {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT },
545 {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT },
546 {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL },
547 {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT },
548 {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT },
549 {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_ALL },
550
551 {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT },
552 {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT },
553 {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT },
554 {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL },
555 {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT },
556 {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT },
557 {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_ALL },
558
559 {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT },
560 {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT },
561 {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT },
562 {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL },
563 {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT },
564 {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT },
565 {"\x1b[1;8P", TB_KEY_F1, TB_MOD_ALL },
566
567 {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT },
568 {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT },
569 {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT },
570 {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL },
571 {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT },
572 {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT },
573 {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_ALL },
574
575 {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT },
576 {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT },
577 {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT },
578 {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL },
579 {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT },
580 {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT },
581 {"\x1b[1;8R", TB_KEY_F3, TB_MOD_ALL },
582
583 {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT },
584 {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT },
585 {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT },
586 {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL },
587 {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT },
588 {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT },
589 {"\x1b[1;8S", TB_KEY_F4, TB_MOD_ALL },
590
591 {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT },
592 {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT },
593 {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT },
594 {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL },
595 {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT },
596 {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT },
597 {"\x1b[15;8~", TB_KEY_F5, TB_MOD_ALL },
598
599 {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT },
600 {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT },
601 {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT },
602 {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL },
603 {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT },
604 {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT },
605 {"\x1b[17;8~", TB_KEY_F6, TB_MOD_ALL },
606
607 {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT },
608 {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT },
609 {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT },
610 {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL },
611 {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT },
612 {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT },
613 {"\x1b[18;8~", TB_KEY_F7, TB_MOD_ALL },
614
615 {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT },
616 {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT },
617 {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT },
618 {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL },
619 {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT },
620 {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT },
621 {"\x1b[19;8~", TB_KEY_F8, TB_MOD_ALL },
622
623 {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT },
624 {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT },
625 {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT },
626 {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL },
627 {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT },
628 {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT },
629 {"\x1b[20;8~", TB_KEY_F9, TB_MOD_ALL },
630
631 {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT },
632 {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT },
633 {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT },
634 {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL },
635 {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT },
636 {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT },
637 {"\x1b[21;8~", TB_KEY_F10, TB_MOD_ALL },
638
639 {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT },
640 {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT },
641 {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT },
642 {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL },
643 {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT },
644 {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT },
645 {"\x1b[23;8~", TB_KEY_F11, TB_MOD_ALL },
646
647 {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT },
648 {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT },
649 {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT },
650 {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL },
651 {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT },
652 {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT },
653 {"\x1b[24;8~", TB_KEY_F12, TB_MOD_ALL },
654
655 /* rxvt arrows */
656 {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT },
657 {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT },
658 {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT },
659 {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL },
660 {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT },
661
662 {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT },
663 {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT },
664 {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT },
665 {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL },
666 {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT },
667
668 {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT },
669 {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT },
670 {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT },
671 {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL },
672 {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT },
673
674 {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT },
675 {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT },
676 {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT },
677 {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL },
678 {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT },
679
680 /* rxvt keys */
681 {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT },
682 {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT },
683 {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT },
684 {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL },
685 {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT },
686 {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT },
687 {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_ALL },
688
689 {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT },
690 {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT },
691 {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL },
692 {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT },
693 {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_ALL },
694 {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT },
695 {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT },
696
697 {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT },
698 {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT },
699 {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL },
700 {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT },
701 {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_ALL },
702 {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT },
703 {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT },
704
705 {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT },
706 {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT },
707 {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL },
708 {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT },
709 {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_ALL },
710 {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT },
711 {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT },
712
713 {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT },
714 {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT },
715 {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL },
716 {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT },
717 {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_ALL },
718 {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT },
719 {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT },
720
721 {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT },
722 {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT },
723 {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL },
724 {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT },
725 {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_ALL },
726 {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT },
727 {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT },
728
729 {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT },
730 {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT },
731 {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL },
732 {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT },
733 {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_ALL },
734 {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT },
735 {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT },
736
737 {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT },
738 {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT },
739 {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL },
740 {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT },
741 {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_ALL },
742 {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT },
743 {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT },
744
745 {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT },
746 {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT },
747 {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL },
748 {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT },
749 {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_ALL },
750 {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT },
751 {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT },
752
753 {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT },
754 {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT },
755 {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL },
756 {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT },
757 {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_ALL },
758 {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT },
759 {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT },
760
761 {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT },
762 {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT },
763 {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL },
764 {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT },
765 {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_ALL },
766 {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT },
767 {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT },
768
769 {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT },
770 {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT },
771 {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL },
772 {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT },
773 {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_ALL },
774 {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT },
775 {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT },
776
777 {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT },
778 {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT },
779 {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL },
780 {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT },
781 {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_ALL },
782 {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT },
783 {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT },
784
785 {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT },
786 {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT },
787 {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL },
788 {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT },
789 {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_ALL },
790 {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT },
791 {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT },
792
793 {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT },
794 {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT },
795 {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL },
796 {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT },
797 {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_ALL },
798 {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT },
799 {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT },
800
801 {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT },
802 {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT },
803 {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL },
804 {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT },
805 {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_ALL },
806 {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT },
807 {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT },
808
809 {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT },
810 {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT },
811 {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL },
812 {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT },
813 {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_ALL },
814 {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT },
815 {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT },
816
817 {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT },
818 {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT },
819 {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL },
820 {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT },
821 {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_ALL },
822 {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT },
823 {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT },
824
825 /* linux console/putty arrows */
826 {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT },
827 {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT },
828 {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT },
829 {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT },
830
831 /* more putty arrows */
832 {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL },
833 {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT },
834 {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL },
835 {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT },
836 {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL },
837 {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT },
838 {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL },
839 {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT },
840
841 {NULL, 0, 0 },
842 };
843
844 static const unsigned char utf8_length[256] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
845 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
846 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
847 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
848 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
849 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
850 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
851 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
852 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
853 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
854 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5,
855 5, 6, 6, 1, 1};
856
857 static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01};
858
859 static int tb_reset(void);
860 static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg,
861 size_t *out_w, const char *fmt, va_list vl);
862 static int init_term_attrs(void);
863 static int init_term_caps(void);
864 static int init_cap_trie(void);
865 static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod);
866 static int cap_trie_find(const char *buf, size_t nbuf,
867 struct cap_trie_t **last, size_t *depth);
868 static int cap_trie_deinit(struct cap_trie_t *node);
869 static int init_resize_handler(void);
870 static int send_init_escape_codes(void);
871 static int send_clear(void);
872 static int update_term_size(void);
873 static int update_term_size_via_esc(void);
874 static int init_cellbuf(void);
875 static int tb_deinit(void);
876 static int load_terminfo(void);
877 static int load_terminfo_from_path(const char *path, const char *term);
878 static int read_terminfo_path(const char *path);
879 static int parse_terminfo_caps(void);
880 static int load_builtin_caps(void);
881 static const char *get_terminfo_string(int16_t str_offsets_pos,
882 int16_t str_table_pos, int16_t str_table_len,
883 int16_t str_index);
884 static int wait_event(struct tb_event *event, int timeout);
885 static int extract_event(struct tb_event *event);
886 static int extract_esc(struct tb_event *event);
887 static int extract_esc_user(struct tb_event *event, int is_post);
888 static int extract_esc_cap(struct tb_event *event);
889 static int extract_esc_mouse(struct tb_event *event);
890 static int resize_cellbufs(void);
891 static void handle_resize(int sig);
892 static int send_attr(uintattr_t fg, uintattr_t bg);
893 static int send_sgr(uintattr_t fg, uintattr_t bg, uintattr_t fg_is_default,
894 uintattr_t bg_is_default);
895 static int send_cursor_if(int x, int y);
896 static int send_char(int x, int y, uint32_t ch);
897 static int send_cluster(int x, int y, uint32_t *ch, size_t nch);
898 static int convert_num(uint32_t num, char *buf);
899 static int cell_cmp(struct tb_cell *a, struct tb_cell *b);
900 static int cell_copy(struct tb_cell *dst, struct tb_cell *src);
901 static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
902 uintattr_t fg, uintattr_t bg);
903 static int cell_reserve_ech(struct tb_cell *cell, size_t n);
904 static int cell_free(struct tb_cell *cell);
905 static int cellbuf_init(struct cellbuf_t *c, int w, int h);
906 static int cellbuf_free(struct cellbuf_t *c);
907 static int cellbuf_clear(struct cellbuf_t *c);
908 static int cellbuf_get(struct cellbuf_t *c, int x, int y,
909 struct tb_cell **out);
910 static int cellbuf_resize(struct cellbuf_t *c, int w, int h);
911 static int bytebuf_puts(struct bytebuf_t *b, const char *str);
912 static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr);
913 static int bytebuf_shift(struct bytebuf_t *b, size_t n);
914 static int bytebuf_flush(struct bytebuf_t *b, int fd);
915 static int bytebuf_reserve(struct bytebuf_t *b, size_t sz);
916 static int bytebuf_free(struct bytebuf_t *b);
917
918 int tb_init(void) {
919 return tb_init_file("/dev/tty");
920 }
921
922 int tb_init_file(const char *path) {
923
924 int ttyfd;
925
926 if (global.initialized) {
927 return TB_ERR_INIT_ALREADY;
928 }
929 ttyfd = open(path, O_RDWR);
930 if (ttyfd < 0) {
931 global.last_errno = errno;
932 return TB_ERR_INIT_OPEN;
933 }
934 global.ttyfd_open = 1;
935 return tb_init_fd(ttyfd);
936 }
937
938 int tb_init_fd(int ttyfd) {
939 return tb_init_rwfd(ttyfd, ttyfd);
940 }
941
942 int tb_init_rwfd(int rfd, int wfd) {
943 int rv;
944 const char *term;
945
946 tb_reset();
947 global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1;
948 global.rfd = rfd;
949 global.wfd = wfd;
950
951 term = getenv("TERM");
952 if (!term) {
953 term = "xterm";
954 setenv("TERM", term, 0);
955 }
956
957 if (strcmp("st-256color", term) == 0) {
958 setenv("TERM", "screen-256color", 1);
959 } else if (strcmp("st", term) == 0) {
960 setenv("TERM", "screen", 1);
961 }
962
963 do {
964 retry:
965 if_err_break(rv, init_term_attrs());
966 if_err_break(rv, init_term_caps());
967 if_err_break(rv, init_cap_trie());
968 if_err_break(rv, init_resize_handler());
969 if_err_break(rv, send_init_escape_codes());
970 if_err_break(rv, send_clear());
971 if_err_break(rv, update_term_size());
972 if_err_break(rv, init_cellbuf());
973 global.initialized = 1;
974 } while (0);
975
976 if (rv != TB_OK) {
977 if (strcmp(term, "xterm")) {
978 setenv("TERM", "xterm", 1);
979 goto retry;
980 }
981 tb_deinit();
982 }
983
984 return rv;
985 }
986
987 int tb_shutdown(void) {
988 if_not_init_return();
989 tb_deinit();
990 return TB_OK;
991 }
992
993 int tb_width(void) {
994 if_not_init_return();
995 return global.width;
996 }
997
998 int tb_height(void) {
999 if_not_init_return();
1000 return global.height;
1001 }
1002
1003 int tb_clear(void) {
1004 if_not_init_return();
1005 return cellbuf_clear(&global.back);
1006 }
1007
1008 int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) {
1009 if_not_init_return();
1010 global.fg = fg;
1011 global.bg = bg;
1012 return TB_OK;
1013 }
1014
1015 int tb_present(void) {
1016
1017 int rv, x, y, i;
1018
1019 if_not_init_return();
1020
1021 global.last_x = -1;
1022 global.last_y = -1;
1023
1024 for (y = 0; y < global.front.height; y++) {
1025 for (x = 0; x < global.front.width;) {
1026 struct tb_cell *back, *front;
1027 int w;
1028 if_err_return(rv, cellbuf_get(
1029 &global.back, x, y, &back));
1030 if_err_return(rv, cellbuf_get(
1031 &global.front, x, y, &front));
1032
1033 {
1034 #ifdef TB_OPT_EGC
1035 if (back->nech > 0)
1036 w = wcswidth((wchar_t *)back->ech, back->nech);
1037 else
1038 #endif
1039 /* wcwidth() simply returns -1 on overflow of
1040 * wchar_t */
1041 w = wcwidth((wchar_t)back->ch);
1042 }
1043 if (w < 1) {
1044 w = 1;
1045 }
1046
1047 if (!cell_cmp(back, front)) {
1048 x += w;
1049 continue;
1050 }
1051 cell_copy(front, back);
1052
1053 send_attr(back->fg, back->bg);
1054 if (w > 1 && x >= global.front.width - (w - 1)) {
1055 for (i = x; i < global.front.width; i++) {
1056 send_char(i, y, ' ');
1057 }
1058 } else {
1059 {
1060 #ifdef TB_OPT_EGC
1061 if (back->nech > 0)
1062 send_cluster(x, y, back->ech,
1063 back->nech);
1064 else
1065 #endif
1066 send_char(x, y, back->ch);
1067 }
1068 for (i = 1; i < w; i++) {
1069 struct tb_cell *front_wide;
1070 if_err_return(rv, cellbuf_get(
1071 &global.front, x + i, y,
1072 &front_wide));
1073 if_err_return(rv, cell_set(front_wide,
1074 0, 1, back->fg, back->bg));
1075
1076 }
1077 }
1078 x += w;
1079 }
1080 }
1081
1082 if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
1083 if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
1084
1085 return TB_OK;
1086 }
1087
1088 int tb_set_cursor(int cx, int cy) {
1089 int rv;
1090 if_not_init_return();
1091 if (cx < 0)
1092 cx = 0;
1093 if (cy < 0)
1094 cy = 0;
1095 if (global.cursor_x == -1) {
1096 if_err_return(rv, bytebuf_puts(&global.out,
1097 global.caps[TB_CAP_SHOW_CURSOR]));
1098 }
1099 if_err_return(rv, send_cursor_if(cx, cy));
1100 global.cursor_x = cx;
1101 global.cursor_y = cy;
1102 return TB_OK;
1103 }
1104
1105 int tb_hide_cursor(void) {
1106 int rv;
1107 if_not_init_return();
1108 if (global.cursor_x >= 0) {
1109 if_err_return(rv, bytebuf_puts(&global.out,
1110 global.caps[TB_CAP_HIDE_CURSOR]));
1111 }
1112 global.cursor_x = -1;
1113 global.cursor_y = -1;
1114 return TB_OK;
1115 }
1116
1117 int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) {
1118 if_not_init_return();
1119 return tb_set_cell_ex(x, y, &ch, 1, fg, bg);
1120 }
1121
1122 int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
1123 uintattr_t bg) {
1124 int rv;
1125 struct tb_cell *cell;
1126 if_not_init_return();
1127 if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1128 if_err_return(rv, cell_set(cell, ch, nch, fg, bg));
1129 return TB_OK;
1130 }
1131
1132 int tb_extend_cell(int x, int y, uint32_t ch) {
1133 if_not_init_return();
1134 #ifdef TB_OPT_EGC
1135 int rv;
1136 struct tb_cell *cell;
1137 size_t nech;
1138 if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1139 if (cell->nech > 0) { /* append to ech */
1140 nech = cell->nech + 1;
1141 if_err_return(rv, cell_reserve_ech(cell, nech));
1142 cell->ech[nech - 1] = ch;
1143 } else { /* make new ech */
1144 nech = 2;
1145 if_err_return(rv, cell_reserve_ech(cell, nech));
1146 cell->ech[0] = cell->ch;
1147 cell->ech[1] = ch;
1148 }
1149 cell->ech[nech] = '\0';
1150 cell->nech = nech;
1151 return TB_OK;
1152 #else
1153 (void)x;
1154 (void)y;
1155 (void)ch;
1156 return TB_ERR;
1157 #endif
1158 }
1159
1160 int tb_set_input_mode(int mode) {
1161 if_not_init_return();
1162 if (mode == TB_INPUT_CURRENT) {
1163 return global.input_mode;
1164 }
1165
1166 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) {
1167 mode |= TB_INPUT_ESC;
1168 }
1169
1170 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) ==
1171 (TB_INPUT_ESC | TB_INPUT_ALT)) {
1172 mode &= ~TB_INPUT_ALT;
1173 }
1174
1175 if (mode & TB_INPUT_MOUSE) {
1176 bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE);
1177 bytebuf_flush(&global.out, global.wfd);
1178 } else {
1179 bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
1180 bytebuf_flush(&global.out, global.wfd);
1181 }
1182
1183 global.input_mode = mode;
1184 return TB_OK;
1185 }
1186
1187 int tb_set_output_mode(int mode) {
1188 if_not_init_return();
1189 switch (mode) {
1190 case TB_OUTPUT_CURRENT:
1191 return global.output_mode;
1192 case TB_OUTPUT_NORMAL:
1193 case TB_OUTPUT_256:
1194 case TB_OUTPUT_216:
1195 case TB_OUTPUT_GRAYSCALE:
1196 #ifdef TB_OPT_TRUECOLOR
1197 case TB_OUTPUT_TRUECOLOR:
1198 #endif
1199 global.output_mode = mode;
1200 return TB_OK;
1201 }
1202 return TB_ERR;
1203 }
1204
1205 int tb_peek_event(struct tb_event *event, int timeout_ms) {
1206 if_not_init_return();
1207 return wait_event(event, timeout_ms);
1208 }
1209
1210 int tb_poll_event(struct tb_event *event) {
1211 if_not_init_return();
1212 return wait_event(event, -1);
1213 }
1214
1215 int tb_get_fds(int *ttyfd, int *resizefd) {
1216 if_not_init_return();
1217
1218 *ttyfd = global.rfd;
1219 *resizefd = global.resize_pipefd[0];
1220
1221 return TB_OK;
1222 }
1223
1224 int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) {
1225 return tb_print_ex(x, y, fg, bg, NULL, str);
1226 }
1227
1228 int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1229 const char *str) {
1230 int rv;
1231 uint32_t uni;
1232 int w, ix = x;
1233 if (out_w) {
1234 *out_w = 0;
1235 }
1236 while (*str) {
1237 str += tb_utf8_char_to_unicode(&uni, str);
1238 w = wcwidth((wchar_t)uni);
1239 if (w <= 0) {
1240 w = 1;
1241 }
1242 if (w == 0 && x > ix) {
1243 if_err_return(rv, tb_extend_cell(x - 1, y, uni));
1244 } else {
1245 if_err_return(rv, tb_set_cell(x, y, uni, fg, bg));
1246 }
1247 x += w;
1248 if (out_w) {
1249 *out_w += w;
1250 }
1251 }
1252 return TB_OK;
1253 }
1254
1255 int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt,
1256 ...) {
1257 int rv;
1258 va_list vl;
1259 va_start(vl, fmt);
1260 rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl);
1261 va_end(vl);
1262 return rv;
1263 }
1264
1265 int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1266 const char *fmt, ...) {
1267 int rv;
1268 va_list vl;
1269 va_start(vl, fmt);
1270 rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl);
1271 va_end(vl);
1272 return rv;
1273 }
1274
1275 int tb_send(const char *buf, size_t nbuf) {
1276 return bytebuf_nputs(&global.out, buf, nbuf);
1277 }
1278
1279 int tb_sendf(const char *fmt, ...) {
1280 int rv;
1281 char buf[TB_OPT_PRINTF_BUF];
1282 va_list vl;
1283 va_start(vl, fmt);
1284 rv = vsnprintf(buf, sizeof(buf), fmt, vl);
1285 va_end(vl);
1286 if (rv < 0 || rv >= (int)sizeof(buf)) {
1287 return TB_ERR;
1288 }
1289 return tb_send(buf, (size_t)rv);
1290 }
1291
1292 int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) {
1293 switch (fn_type) {
1294 case TB_FUNC_EXTRACT_PRE:
1295 global.fn_extract_esc_pre = fn;
1296 return TB_OK;
1297 case TB_FUNC_EXTRACT_POST:
1298 global.fn_extract_esc_post = fn;
1299 return TB_OK;
1300 }
1301 return TB_ERR;
1302 }
1303
1304 struct tb_cell *tb_cell_buffer(void) {
1305 if (!global.initialized)
1306 return NULL;
1307 return global.back.cells;
1308 }
1309
1310 int tb_utf8_char_length(char c) {
1311 return utf8_length[(unsigned char)c];
1312 }
1313
1314 int tb_utf8_char_to_unicode(uint32_t *out, const char *c) {
1315
1316 int i;
1317 unsigned char len, mask;
1318 uint32_t result;
1319
1320 if (*c == 0) {
1321 return TB_ERR;
1322 }
1323
1324 len = tb_utf8_char_length(*c);
1325 mask = utf8_mask[len - 1];
1326 result = c[0] & mask;
1327 for (i = 1; i < len; ++i) {
1328 result <<= 6;
1329 result |= c[i] & 0x3f;
1330 }
1331
1332 *out = result;
1333 return (int)len;
1334 }
1335
1336 int tb_utf8_unicode_to_char(char *out, uint32_t c) {
1337 int len = 0;
1338 int first;
1339 int i;
1340
1341 if (c < 0x80) {
1342 first = 0;
1343 len = 1;
1344 } else if (c < 0x800) {
1345 first = 0xc0;
1346 len = 2;
1347 } else if (c < 0x10000) {
1348 first = 0xe0;
1349 len = 3;
1350 } else if (c < 0x200000) {
1351 first = 0xf0;
1352 len = 4;
1353 } else if (c < 0x4000000) {
1354 first = 0xf8;
1355 len = 5;
1356 } else {
1357 first = 0xfc;
1358 len = 6;
1359 }
1360
1361 for (i = len - 1; i > 0; --i) {
1362 out[i] = (c & 0x3f) | 0x80;
1363 c >>= 6;
1364 }
1365 out[0] = c | first;
1366
1367 return len;
1368 }
1369
1370 int tb_last_errno(void) {
1371 return global.last_errno;
1372 }
1373
1374 const char *tb_strerror(int err) {
1375 switch (err) {
1376 case TB_OK:
1377 return "Success";
1378 case TB_ERR_NEED_MORE:
1379 return "Not enough input";
1380 case TB_ERR_INIT_ALREADY:
1381 return "Termbox initialized already";
1382 case TB_ERR_MEM:
1383 return "Out of memory";
1384 case TB_ERR_NO_EVENT:
1385 return "No event";
1386 case TB_ERR_NO_TERM:
1387 return "No TERM in environment";
1388 case TB_ERR_NOT_INIT:
1389 return "Termbox not initialized";
1390 case TB_ERR_OUT_OF_BOUNDS:
1391 return "Out of bounds";
1392 case TB_ERR_UNSUPPORTED_TERM:
1393 return "Unsupported terminal";
1394 case TB_ERR_CAP_COLLISION:
1395 return "Termcaps collision";
1396 case TB_ERR_RESIZE_SSCANF:
1397 return "Terminal width/height not received by sscanf()"
1398 " after resize";
1399 case TB_ERR:
1400 case TB_ERR_INIT_OPEN:
1401 case TB_ERR_READ:
1402 case TB_ERR_RESIZE_IOCTL:
1403 case TB_ERR_RESIZE_PIPE:
1404 case TB_ERR_RESIZE_SIGACTION:
1405 case TB_ERR_POLL:
1406 case TB_ERR_TCGETATTR:
1407 case TB_ERR_TCSETATTR:
1408 case TB_ERR_RESIZE_WRITE:
1409 case TB_ERR_RESIZE_POLL:
1410 case TB_ERR_RESIZE_READ:
1411 default:
1412 strerror_r(global.last_errno, global.errbuf,
1413 sizeof(global.errbuf));
1414 return (const char *)global.errbuf;
1415 }
1416 }
1417
1418 int tb_has_truecolor(void) {
1419 #ifdef TB_OPT_TRUECOLOR
1420 return 1;
1421 #else
1422 return 0;
1423 #endif
1424 }
1425
1426 int tb_has_egc(void) {
1427 #ifdef TB_OPT_EGC
1428 return 1;
1429 #else
1430 return 0;
1431 #endif
1432 }
1433
1434 const char *tb_version(void) {
1435 return TB_VERSION_STR;
1436 }
1437
1438 static int tb_reset(void) {
1439 int ttyfd_open = global.ttyfd_open;
1440 memset(&global, 0, sizeof(global));
1441 global.ttyfd = -1;
1442 global.rfd = -1;
1443 global.wfd = -1;
1444 global.ttyfd_open = ttyfd_open;
1445 global.resize_pipefd[0] = -1;
1446 global.resize_pipefd[1] = -1;
1447 global.width = -1;
1448 global.height = -1;
1449 global.cursor_x = -1;
1450 global.cursor_y = -1;
1451 global.last_x = -1;
1452 global.last_y = -1;
1453 global.fg = TB_DEFAULT;
1454 global.bg = TB_DEFAULT;
1455 global.last_fg = ~global.fg;
1456 global.last_bg = ~global.bg;
1457 global.input_mode = TB_INPUT_ESC;
1458 global.output_mode = TB_OUTPUT_NORMAL;
1459 return TB_OK;
1460 }
1461
1462 static int init_term_attrs(void) {
1463
1464 struct termios tios;
1465
1466 if (global.ttyfd < 0) {
1467 return TB_OK;
1468 }
1469
1470 if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) {
1471 global.last_errno = errno;
1472 return TB_ERR_TCGETATTR;
1473 }
1474
1475 memcpy(&tios, &global.orig_tios, sizeof(tios));
1476 global.has_orig_tios = 1;
1477
1478 cfmakeraw(&tios);
1479 tios.c_cc[VMIN] = 1;
1480 tios.c_cc[VTIME] = 0;
1481
1482 if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) {
1483 global.last_errno = errno;
1484 return TB_ERR_TCSETATTR;
1485 }
1486
1487 return TB_OK;
1488 }
1489
1490 int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1491 const char *fmt, va_list vl) {
1492 int rv;
1493 char buf[TB_OPT_PRINTF_BUF];
1494 rv = vsnprintf(buf, sizeof(buf), fmt, vl);
1495 if (rv < 0 || rv >= (int)sizeof(buf)) {
1496 return TB_ERR;
1497 }
1498 return tb_print_ex(x, y, fg, bg, out_w, buf);
1499 }
1500
1501 static int init_term_caps(void) {
1502 if (load_terminfo() == TB_OK) {
1503 return parse_terminfo_caps();
1504 }
1505 return load_builtin_caps();
1506 }
1507
1508 static int init_cap_trie(void) {
1509 int rv, i;
1510
1511 /* Add caps from terminfo or built-in */
1512 for (i = 0; i < TB_CAP__COUNT_KEYS; i++) {
1513 if_err_return(rv,
1514 cap_trie_add(global.caps[i], tb_key_i(i), 0));
1515 }
1516
1517 /* Add built-in mod caps */
1518 for (i = 0; builtin_mod_caps[i].cap != NULL; i++) {
1519 rv = cap_trie_add(builtin_mod_caps[i].cap,
1520 builtin_mod_caps[i].key,
1521 builtin_mod_caps[i].mod);
1522 /*
1523 * Collisions are OK. This can happen if global.caps collides
1524 * with builtin_mod_caps. It is desirable to give precedence to
1525 * global.caps here.
1526 */
1527 if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) {
1528 return rv;
1529 }
1530 }
1531
1532 return TB_OK;
1533 }
1534
1535 static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) {
1536 struct cap_trie_t *next, *node = &global.cap_trie;
1537 size_t i, j;
1538 for (i = 0; cap[i] != '\0'; i++) {
1539 char c = cap[i];
1540 next = NULL;
1541
1542 /* Check if c is already a child of node */
1543 for (j = 0; j < node->nchildren; j++) {
1544 if (node->children[j].c == c) {
1545 next = &node->children[j];
1546 break;
1547 }
1548 }
1549 if (!next) {
1550 /* We need to add a new child to node */
1551 node->nchildren += 1;
1552 node->children = tb_realloc(node->children,
1553 sizeof(*node) * node->nchildren);
1554 if (!node->children) {
1555 return TB_ERR_MEM;
1556 }
1557 next = &node->children[node->nchildren - 1];
1558 memset(next, 0, sizeof(*next));
1559 next->c = c;
1560 }
1561
1562 /* Continue */
1563 node = next;
1564 }
1565
1566 if (node->is_leaf) {
1567 /* Already a leaf here */
1568 return TB_ERR_CAP_COLLISION;
1569 }
1570
1571 node->is_leaf = 1;
1572 node->key = key;
1573 node->mod = mod;
1574 return TB_OK;
1575 }
1576
1577 static int cap_trie_find(const char *buf, size_t nbuf,
1578 struct cap_trie_t **last, size_t *depth) {
1579 struct cap_trie_t *next, *node = &global.cap_trie;
1580 size_t i, j;
1581 *last = node;
1582 *depth = 0;
1583 for (i = 0; i < nbuf; i++) {
1584 char c = buf[i];
1585 next = NULL;
1586
1587 /* Find c in node.children */
1588 for (j = 0; j < node->nchildren; j++) {
1589 if (node->children[j].c == c) {
1590 next = &node->children[j];
1591 break;
1592 }
1593 }
1594 if (!next) {
1595 /* Not found */
1596 return TB_OK;
1597 }
1598 node = next;
1599 *last = node;
1600 *depth += 1;
1601 if (node->is_leaf && node->nchildren < 1) {
1602 break;
1603 }
1604 }
1605 return TB_OK;
1606 }
1607
1608 static int cap_trie_deinit(struct cap_trie_t *node) {
1609 size_t j;
1610 for (j = 0; j < node->nchildren; j++) {
1611 cap_trie_deinit(&node->children[j]);
1612 }
1613 if (node->children) {
1614 tb_free(node->children);
1615 }
1616 memset(node, 0, sizeof(*node));
1617 return TB_OK;
1618 }
1619
1620 static int init_resize_handler(void) {
1621 struct sigaction sa;
1622 if (pipe(global.resize_pipefd) != 0) {
1623 global.last_errno = errno;
1624 return TB_ERR_RESIZE_PIPE;
1625 }
1626
1627 memset(&sa, 0, sizeof(sa));
1628 sa.sa_handler = handle_resize;
1629 if (sigaction(SIGWINCH, &sa, NULL) != 0) {
1630 global.last_errno = errno;
1631 return TB_ERR_RESIZE_SIGACTION;
1632 }
1633
1634 return TB_OK;
1635 }
1636
1637 static int send_init_escape_codes(void) {
1638 int rv;
1639 if_err_return(rv, bytebuf_puts(&global.out,
1640 global.caps[TB_CAP_ENTER_CA]));
1641 if_err_return(rv, bytebuf_puts(&global.out,
1642 global.caps[TB_CAP_ENTER_KEYPAD]));
1643 if_err_return(rv, bytebuf_puts(&global.out,
1644 global.caps[TB_CAP_HIDE_CURSOR]));
1645 return TB_OK;
1646 }
1647
1648 static int send_clear(void) {
1649 int rv;
1650
1651 if_err_return(rv, send_attr(global.fg, global.bg));
1652 if_err_return(rv, bytebuf_puts(&global.out,
1653 global.caps[TB_CAP_CLEAR_SCREEN]));
1654
1655 if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
1656 if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
1657
1658 global.last_x = -1;
1659 global.last_y = -1;
1660
1661 return TB_OK;
1662 }
1663
1664 static int update_term_size(void) {
1665 int rv, ioctl_errno;
1666 struct winsize sz;
1667
1668 if (global.ttyfd < 0) {
1669 return TB_OK;
1670 }
1671
1672 memset(&sz, 0, sizeof(sz));
1673
1674 /* Try ioctl TIOCGWINSZ */
1675 if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) {
1676 global.width = sz.ws_col;
1677 global.height = sz.ws_row;
1678 return TB_OK;
1679 }
1680 ioctl_errno = errno;
1681
1682 /* Try >cursor(9999,9999), >u7, <u6 */
1683 if_ok_return(rv, update_term_size_via_esc());
1684
1685 global.last_errno = ioctl_errno;
1686 return TB_ERR_RESIZE_IOCTL;
1687 }
1688
1689 static int update_term_size_via_esc(void) {
1690 #ifndef TB_RESIZE_FALLBACK_MS
1691 #define TB_RESIZE_FALLBACK_MS 1000
1692 #endif
1693
1694 char *move_and_report = "\x1b[9999;9999H\x1b[6n";
1695 int rw, rh;
1696 char buf[TB_OPT_READ_BUF];
1697 ssize_t write_rv, read_rv;
1698 int select_rv;
1699 struct timeval timeout;
1700 fd_set fds;
1701
1702 write_rv = write(global.wfd, move_and_report, strlen(move_and_report));
1703 if (write_rv != (ssize_t)strlen(move_and_report)) {
1704 return TB_ERR_RESIZE_WRITE;
1705 }
1706
1707 FD_ZERO(&fds);
1708 FD_SET(global.rfd, &fds);
1709
1710 timeout.tv_sec = 0;
1711 timeout.tv_usec = TB_RESIZE_FALLBACK_MS * 1000;
1712
1713 select_rv = select(global.rfd + 1, &fds, NULL, NULL, &timeout);
1714
1715 if (select_rv != 1) {
1716 global.last_errno = errno;
1717 return TB_ERR_RESIZE_POLL;
1718 }
1719
1720 read_rv = read(global.rfd, buf, sizeof(buf) - 1);
1721 if (read_rv < 1) {
1722 global.last_errno = errno;
1723 return TB_ERR_RESIZE_READ;
1724 }
1725 buf[read_rv] = '\0';
1726
1727 if (sscanf(buf, "\x1b[%d;%dR", &rh, &rw) != 2) {
1728 return TB_ERR_RESIZE_SSCANF;
1729 }
1730
1731 global.width = rw;
1732 global.height = rh;
1733 return TB_OK;
1734 }
1735
1736 static int init_cellbuf(void) {
1737 int rv;
1738 if_err_return(rv, cellbuf_init(&global.back, global.width,
1739 global.height));
1740 if_err_return(rv, cellbuf_init(&global.front, global.width,
1741 global.height));
1742 if_err_return(rv, cellbuf_clear(&global.back));
1743 if_err_return(rv, cellbuf_clear(&global.front));
1744 return TB_OK;
1745 }
1746
1747 static int tb_deinit(void) {
1748
1749 struct sigaction sig = {0};
1750
1751 if (global.caps[0] != NULL && global.wfd >= 0) {
1752 bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
1753 bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]);
1754 bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]);
1755 bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]);
1756 bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]);
1757 bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
1758 bytebuf_flush(&global.out, global.wfd);
1759 }
1760 if (global.ttyfd >= 0) {
1761 if (global.has_orig_tios) {
1762 tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios);
1763 }
1764 if (global.ttyfd_open) {
1765 close(global.ttyfd);
1766 global.ttyfd_open = 0;
1767 }
1768 }
1769
1770 sig.sa_handler = SIG_DFL;
1771 sigaction(SIGWINCH, &sig, NULL);
1772 if (global.resize_pipefd[0] >= 0)
1773 close(global.resize_pipefd[0]);
1774 if (global.resize_pipefd[1] >= 0)
1775 close(global.resize_pipefd[1]);
1776
1777 cellbuf_free(&global.back);
1778 cellbuf_free(&global.front);
1779 bytebuf_free(&global.in);
1780 bytebuf_free(&global.out);
1781
1782 if (global.terminfo)
1783 tb_free(global.terminfo);
1784
1785 cap_trie_deinit(&global.cap_trie);
1786
1787 tb_reset();
1788 return TB_OK;
1789 }
1790
1791 static int load_terminfo(void) {
1792 int rv;
1793 char tmp[PATH_MAX];
1794 const char *term, *terminfo, *home, *dirs;
1795
1796 /*
1797 * See terminfo(5) "Fetching Compiled Descriptions" for a description
1798 * of this behavior. Some of these paths are compile-time ncurses
1799 * options, so best guesses are used here.
1800 */
1801 term = getenv("TERM");
1802 if (!term) {
1803 return TB_ERR;
1804 }
1805
1806 /* If TERMINFO is set, try that directory and stop */
1807 terminfo = getenv("TERMINFO");
1808 if (terminfo) {
1809 return load_terminfo_from_path(terminfo, term);
1810 }
1811
1812 /* Next try ~/.terminfo */
1813 home = getenv("HOME");
1814 if (home) {
1815 if_err_return(rv, snprintf_(tmp, sizeof(tmp), "%s/.terminfo",
1816 home));
1817 if_ok_return(rv, load_terminfo_from_path(tmp, term));
1818 }
1819
1820 /*
1821 * Next try TERMINFO_DIRS
1822 *
1823 * Note, empty entries are supposed to be interpretted as the
1824 * "compiled-in default", which is of course system-dependent.
1825 * Previously /etc/terminfo was used here. Let's skip empty entries
1826 * altogether rather than give precedence to a guess, and check common
1827 * paths after this loop.
1828 */
1829 dirs = getenv("TERMINFO_DIRS");
1830 if (dirs) {
1831 char *dir;
1832 if_err_return(rv, snprintf_(tmp, sizeof(tmp), "%s", dirs));
1833 dir = strtok(tmp, ":");
1834 while (dir) {
1835 const char *cdir = dir;
1836 if (*cdir != '\0') {
1837 if_ok_return(rv,
1838 load_terminfo_from_path(cdir, term));
1839 }
1840 dir = strtok(NULL, ":");
1841 }
1842 }
1843
1844 #ifdef TB_TERMINFO_DIR
1845 if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term));
1846 #endif
1847 if_ok_return(rv,
1848 load_terminfo_from_path("/usr/local/etc/terminfo", term));
1849 if_ok_return(rv,
1850 load_terminfo_from_path("/usr/local/share/terminfo", term));
1851 if_ok_return(rv,
1852 load_terminfo_from_path("/usr/local/lib/terminfo", term));
1853 if_ok_return(rv,
1854 load_terminfo_from_path("/etc/terminfo", term));
1855 if_ok_return(rv,
1856 load_terminfo_from_path("/usr/share/terminfo", term));
1857 if_ok_return(rv,
1858 load_terminfo_from_path("/usr/lib/terminfo", term));
1859 if_ok_return(rv,
1860 load_terminfo_from_path("/usr/share/lib/terminfo", term));
1861 if_ok_return(rv,
1862 load_terminfo_from_path("/lib/terminfo", term));
1863
1864 return TB_ERR;
1865 }
1866
1867 static int load_terminfo_from_path(const char *path, const char *term) {
1868 char tmp[PATH_MAX];
1869 int rv;
1870
1871 /* Look for term at this terminfo location, e.g., <terminfo>/x/xterm */
1872 if_err_return(rv, snprintf_(tmp, sizeof(tmp),
1873 "%s/%c/%s", path, term[0], term));
1874 if_ok_return(rv, read_terminfo_path(tmp));
1875
1876 #ifdef __APPLE__
1877 /* Try the Darwin equivalent path, e.g., <terminfo>/78/xterm */
1878 if_err_return(rv, snprintf_(tmp, sizeof(tmp),
1879 "%s/%x/%s", path, term[0], term));
1880 return read_terminfo_path(tmp);
1881 #endif
1882
1883 return TB_ERR;
1884 }
1885
1886 static int read_terminfo_path(const char *path) {
1887 size_t fsize;
1888 char *data;
1889 struct stat st;
1890 FILE *fp = fopen(path, "rb");
1891 if (!fp) {
1892 return TB_ERR;
1893 }
1894
1895 if (fstat(fileno(fp), &st) != 0) {
1896 fclose(fp);
1897 return TB_ERR;
1898 }
1899
1900 fsize = st.st_size;
1901 data = tb_malloc(fsize);
1902 if (!data) {
1903 fclose(fp);
1904 return TB_ERR;
1905 }
1906
1907 if (fread(data, 1, fsize, fp) != fsize) {
1908 fclose(fp);
1909 tb_free(data);
1910 return TB_ERR;
1911 }
1912
1913 global.terminfo = data;
1914 global.nterminfo = fsize;
1915
1916 fclose(fp);
1917 return TB_OK;
1918 }
1919
1920 static int parse_terminfo_caps(void) {
1921 /*
1922 * See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT"
1923 * for a description of this behavior.
1924 */
1925 int16_t *header;
1926 int bytes_per_int, align_offset, pos_str_offsets, pos_str_table, i;
1927
1928 /* Ensure there's at least a header's worth of data */
1929 if (global.nterminfo < 6) {
1930 return TB_ERR;
1931 }
1932
1933 header = (int16_t *)global.terminfo;
1934 /*
1935 * header[0] the magic number (octal 0432 or 01036)
1936 * header[1] the size, in bytes, of the names section
1937 * header[2] the number of bytes in the boolean section
1938 * header[3] the number of short integers in the numbers section
1939 * header[4] the number of offsets in the strings section
1940 * header[5] the size, in bytes, of the string table
1941 */
1942
1943 /* Legacy ints are 16-bit, extended ints are 32-bit */
1944 bytes_per_int = header[0] == 01036 ? 4 /* 32-bit */
1945 : 2; /* 16-bit */
1946
1947 /*
1948 * Between the boolean section and the number section, a null byte
1949 * will be inserted, if necessary, to ensure that the number section
1950 * begins on an even byte
1951 */
1952 align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0;
1953
1954 pos_str_offsets =
1955 (6 * sizeof(int16_t)) /* header (12 bytes) */
1956 + header[1] /* length of names section */
1957 + header[2] /* length of boolean section */
1958 + align_offset +
1959 (header[3] * bytes_per_int); /* length of numbers section */
1960
1961 /* length of string offsets table */
1962 pos_str_table = pos_str_offsets + (header[4] * sizeof(int16_t));
1963
1964 /* Load caps */
1965 for (i = 0; i < TB_CAP__COUNT; i++) {
1966 const char *cap = get_terminfo_string(pos_str_offsets,
1967 pos_str_table, header[5],
1968 terminfo_cap_indexes[i]);
1969 if (!cap) {
1970 /* Something is not right */
1971 return TB_ERR;
1972 }
1973 global.caps[i] = cap;
1974 }
1975
1976 return TB_OK;
1977 }
1978
1979 static int load_builtin_caps(void) {
1980 int i, j;
1981 const char *term = getenv("TERM");
1982
1983 if (!term) {
1984 return TB_ERR_NO_TERM;
1985 }
1986
1987 /* Check for exact TERM match */
1988 for (i = 0; builtin_terms[i].name != NULL; i++) {
1989 if (strcmp(term, builtin_terms[i].name) == 0) {
1990 for (j = 0; j < TB_CAP__COUNT; j++) {
1991 global.caps[j] = builtin_terms[i].caps[j];
1992 }
1993 return TB_OK;
1994 }
1995 }
1996
1997 /* Check for partial TERM or alias match */
1998 for (i = 0; builtin_terms[i].name != NULL; i++) {
1999 if (strstr(term, builtin_terms[i].name) != NULL ||
2000 (*(builtin_terms[i].alias) != '\0' &&
2001 strstr(term, builtin_terms[i].alias) != NULL))
2002 {
2003 for (j = 0; j < TB_CAP__COUNT; j++) {
2004 global.caps[j] = builtin_terms[i].caps[j];
2005 }
2006 return TB_OK;
2007 }
2008 }
2009
2010 return TB_ERR_UNSUPPORTED_TERM;
2011 }
2012
2013 static const char *get_terminfo_string(int16_t str_offsets_pos,
2014 int16_t str_table_pos, int16_t str_table_len,
2015 int16_t str_index) {
2016 const int16_t *str_offset =
2017 (int16_t *)(global.terminfo + (int)str_offsets_pos +
2018 ((int)str_index * (int)sizeof(int16_t)));
2019 if (*str_offset < 0) {
2020 /* A negative indicates the cap is absent from this terminal */
2021 return "";
2022 }
2023 if (*str_offset >= str_table_len) {
2024 /* Invalid string offset */
2025 return NULL;
2026 }
2027 if (((size_t)((int)str_table_pos + (int)*str_offset)) >=
2028 global.nterminfo) {
2029 /* Truncated/corrupt terminfo? */
2030 return NULL;
2031 }
2032 return (const char *)(global.terminfo + (int)str_table_pos +
2033 (int)*str_offset);
2034 }
2035
2036 static int wait_event(struct tb_event *event, int timeout) {
2037 int rv;
2038 char buf[TB_OPT_READ_BUF];
2039 fd_set fds;
2040 struct timeval tv;
2041
2042 memset(event, 0, sizeof(*event));
2043 if_ok_return(rv, extract_event(event));
2044
2045 tv.tv_sec = timeout / 1000;
2046 tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
2047
2048 do {
2049 int maxfd, select_rv, tty_has_events, resize_has_events;
2050 FD_ZERO(&fds);
2051 FD_SET(global.rfd, &fds);
2052 FD_SET(global.resize_pipefd[0], &fds);
2053
2054 maxfd = global.resize_pipefd[0] > global.rfd
2055 ? global.resize_pipefd[0]
2056 : global.rfd;
2057
2058 select_rv = select(maxfd + 1, &fds, NULL, NULL,
2059 (timeout < 0) ? NULL : &tv);
2060
2061 if (select_rv < 0) {
2062 /* Let EINTR/EAGAIN bubble up */
2063 global.last_errno = errno;
2064 return TB_ERR_POLL;
2065 } else if (select_rv == 0) {
2066 return TB_ERR_NO_EVENT;
2067 }
2068
2069 tty_has_events = (FD_ISSET(global.rfd, &fds));
2070 resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds));
2071
2072 if (tty_has_events) {
2073 ssize_t read_rv = read(global.rfd, buf, sizeof(buf));
2074 if (read_rv < 0) {
2075 global.last_errno = errno;
2076 return TB_ERR_READ;
2077 } else if (read_rv > 0) {
2078 bytebuf_nputs(&global.in, buf, read_rv);
2079 }
2080 }
2081
2082 if (resize_has_events) {
2083 int ignore = 0;
2084 read(global.resize_pipefd[0], &ignore, sizeof(ignore));
2085 /* TODO Harden against errors encountered mid-resize */
2086 if_err_return(rv, update_term_size());
2087 if_err_return(rv, resize_cellbufs());
2088 event->type = TB_EVENT_RESIZE;
2089 event->w = global.width;
2090 event->h = global.height;
2091 return TB_OK;
2092 }
2093
2094 memset(event, 0, sizeof(*event));
2095 if_ok_return(rv, extract_event(event));
2096 } while (timeout == -1);
2097
2098 return rv;
2099 }
2100
2101 static int extract_event(struct tb_event *event) {
2102 int rv;
2103 struct bytebuf_t *in = &global.in;
2104
2105 if (in->len == 0) {
2106 return TB_ERR;
2107 }
2108
2109 if (in->buf[0] == '\x1b') {
2110 /* Escape sequence? */
2111 /* In TB_INPUT_ESC,
2112 * skip if the buffer is a single escape char */
2113 if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) {
2114 if_ok_or_need_more_return(rv, extract_esc(event));
2115 }
2116
2117 /* Escape key? */
2118 if (global.input_mode & TB_INPUT_ESC) {
2119 event->type = TB_EVENT_KEY;
2120 event->ch = 0;
2121 event->key = TB_KEY_ESC;
2122 event->mod = 0;
2123 bytebuf_shift(in, 1);
2124 return TB_OK;
2125 }
2126
2127 /* Recurse for alt key */
2128 event->mod |= TB_MOD_ALT;
2129 bytebuf_shift(in, 1);
2130 return extract_event(event);
2131 }
2132
2133 /* ASCII control key? */
2134 if ((uint16_t)in->buf[0] < TB_KEY_SPACE ||
2135 in->buf[0] == TB_KEY_BACKSPACE2)
2136 {
2137 event->type = TB_EVENT_KEY;
2138 event->ch = 0;
2139 event->key = (uint16_t)in->buf[0];
2140 event->mod |= TB_MOD_CTRL;
2141 bytebuf_shift(in, 1);
2142 return TB_OK;
2143 }
2144
2145 /* UTF-8? */
2146 if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) {
2147 event->type = TB_EVENT_KEY;
2148 tb_utf8_char_to_unicode(&event->ch, in->buf);
2149 event->key = 0;
2150 bytebuf_shift(in, tb_utf8_char_length(in->buf[0]));
2151 return TB_OK;
2152 }
2153
2154 /* Need more input */
2155 return TB_ERR;
2156 }
2157
2158 static int extract_esc(struct tb_event *event) {
2159 int rv;
2160 if_ok_or_need_more_return(rv, extract_esc_user(event, 0));
2161 if_ok_or_need_more_return(rv, extract_esc_cap(event));
2162 if_ok_or_need_more_return(rv, extract_esc_mouse(event));
2163 if_ok_or_need_more_return(rv, extract_esc_user(event, 1));
2164 return TB_ERR;
2165 }
2166
2167 static int extract_esc_user(struct tb_event *event, int is_post) {
2168 int rv;
2169 size_t consumed = 0;
2170 struct bytebuf_t *in = &global.in;
2171 int (*fn)(struct tb_event *, size_t *);
2172
2173 fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre;
2174
2175 if (!fn) {
2176 return TB_ERR;
2177 }
2178
2179 rv = fn(event, &consumed);
2180 if (rv == TB_OK) {
2181 bytebuf_shift(in, consumed);
2182 }
2183
2184 if_ok_or_need_more_return(rv, rv);
2185 return TB_ERR;
2186 }
2187
2188 static int extract_esc_cap(struct tb_event *event) {
2189 int rv;
2190 struct bytebuf_t *in = &global.in;
2191 struct cap_trie_t *node;
2192 size_t depth;
2193
2194 if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth));
2195 if (node->is_leaf) {
2196 /* Found a leaf node */
2197 event->type = TB_EVENT_KEY;
2198 event->ch = 0;
2199 event->key = node->key;
2200 event->mod = node->mod;
2201 bytebuf_shift(in, depth);
2202 return TB_OK;
2203 } else if (node->nchildren > 0 && in->len <= depth) {
2204 /* Found a branch node (not enough input) */
2205 return TB_ERR_NEED_MORE;
2206 }
2207
2208 return TB_ERR;
2209 }
2210
2211 static int extract_esc_mouse(struct tb_event *event) {
2212 struct bytebuf_t *in = &global.in;
2213
2214 enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX };
2215
2216 char *cmp[TYPE_MAX];
2217
2218 enum type type = 0;
2219 int ret = TB_ERR;
2220 size_t buf_shift = 0;
2221
2222 /* X10 mouse encoding, the simplest one */
2223 /* \x1b [ M Cb Cx Cy */
2224 cmp[TYPE_VT200] = "\x1b[M";
2225 /* xterm 1006 extended mode or urxvt 1015 extended mode */
2226 /* xterm: \x1b [ < Cb ; Cx ; Cy (M or m) */
2227 cmp[TYPE_1006] = "\x1b[<";
2228 /* urxvt: \x1b [ Cb ; Cx ; Cy M */
2229 cmp[TYPE_1015] = "\x1b[";
2230
2231 /* Unrolled at compile-time (probably) */
2232 for (; type < TYPE_MAX; type++) {
2233 size_t size = strlen(cmp[type]);
2234
2235 if (in->len >= size &&
2236 (strncmp(cmp[type], in->buf, size)) == 0) {
2237 break;
2238 }
2239 }
2240
2241 if (type == TYPE_MAX) {
2242 ret = TB_ERR; /* No match */
2243 return ret;
2244 }
2245
2246 switch (type) {
2247 case TYPE_VT200:
2248 {
2249 int b, fail;
2250 if (in->len < 6) break;
2251
2252 b = in->buf[3] - 0x20;
2253 fail = 0;
2254
2255 switch (b & 3) {
2256 case 0:
2257 event->key = ((b & 64) != 0) ?
2258 TB_KEY_MOUSE_WHEEL_UP :
2259 TB_KEY_MOUSE_LEFT;
2260 break;
2261 case 1:
2262 event->key = ((b & 64) != 0) ?
2263 TB_KEY_MOUSE_WHEEL_DOWN :
2264 TB_KEY_MOUSE_MIDDLE;
2265 break;
2266 case 2:
2267 event->key = TB_KEY_MOUSE_RIGHT;
2268 break;
2269 case 3:
2270 event->key = TB_KEY_MOUSE_RELEASE;
2271 break;
2272 default:
2273 ret = TB_ERR;
2274 fail = 1;
2275 break;
2276 }
2277
2278 if (!fail) {
2279 if ((b & 32) != 0) {
2280 event->mod |= TB_MOD_MOTION;
2281 }
2282
2283 /* the coord is 1,1 for upper left */
2284 event->x = ((uint8_t)in->buf[4]) - 0x21;
2285 event->y = ((uint8_t)in->buf[5]) - 0x21;
2286
2287 ret = TB_OK;
2288 }
2289
2290 buf_shift = 6;
2291 }
2292 break;
2293 case TYPE_1006:
2294 /* fallthrough */
2295 case TYPE_1015:
2296 {
2297 size_t i = 0;
2298
2299 enum {
2300 FIRST_M = 0,
2301 FIRST_SEMICOLON,
2302 LAST_SEMICOLON,
2303 FIRST_LAST_MAX
2304 };
2305
2306 size_t indices[FIRST_LAST_MAX] = {
2307 index_fail,
2308 index_fail,
2309 index_fail
2310 };
2311 int m_is_capital = 0;
2312
2313 for (i = 0; i < in->len; i++) {
2314 if (in->buf[i] != ';') {
2315 if (indices[FIRST_SEMICOLON] == index_fail) {
2316 indices[FIRST_SEMICOLON] = i;
2317 } else {
2318 indices[LAST_SEMICOLON] = i;
2319 }
2320 } else if (indices[FIRST_M] == index_fail) {
2321 if (in->buf[i] == 'm' || in->buf[i] == 'M') {
2322 m_is_capital = (in->buf[i] == 'M');
2323 indices[FIRST_M] = i;
2324 }
2325 }
2326 }
2327
2328 if (indices[FIRST_M] == index_fail ||
2329 indices[FIRST_SEMICOLON] == index_fail ||
2330 indices[LAST_SEMICOLON] == index_fail) {
2331 ret = TB_ERR;
2332 } else {
2333 int start = (type == TYPE_1015 ? 2 : 3);
2334 int fail = 0;
2335
2336 unsigned n1 = strtoul(&in->buf[start], NULL, 10);
2337 unsigned n2 = strtoul(
2338 &in->buf[indices[FIRST_SEMICOLON] + 1],
2339 NULL, 10);
2340 unsigned n3 = strtoul(
2341 &in->buf[indices[LAST_SEMICOLON] + 1],
2342 NULL, 10);
2343
2344 if (type == TYPE_1015) {
2345 n1 -= 0x20;
2346 }
2347
2348
2349 switch (n1 & 3) {
2350 case 0:
2351 event->key = ((n1 & 64) != 0)
2352 ? TB_KEY_MOUSE_WHEEL_UP
2353 : TB_KEY_MOUSE_LEFT;
2354 break;
2355 case 1:
2356 event->key = ((n1 & 64) != 0)
2357 ? TB_KEY_MOUSE_WHEEL_DOWN
2358 : TB_KEY_MOUSE_MIDDLE;
2359 break;
2360 case 2:
2361 event->key = TB_KEY_MOUSE_RIGHT;
2362 break;
2363 case 3:
2364 event->key = TB_KEY_MOUSE_RELEASE;
2365 break;
2366 default:
2367 ret = TB_ERR;
2368 fail = 1;
2369 break;
2370 }
2371
2372 buf_shift = in->len;
2373
2374 if (!fail) {
2375 if (!m_is_capital) {
2376 /* on xterm mouse release is signaled
2377 * by lowercase m */
2378 event->key = TB_KEY_MOUSE_RELEASE;
2379 }
2380
2381 if ((n1 & 32) != 0) {
2382 event->mod |= TB_MOD_MOTION;
2383 }
2384
2385 event->x = ((uint8_t)n2) - 1;
2386 event->y = ((uint8_t)n3) - 1;
2387
2388 ret = TB_OK;
2389 }
2390 }
2391 }
2392 break;
2393 case TYPE_MAX:
2394 ret = TB_ERR;
2395 }
2396
2397 if (buf_shift > 0) {
2398 bytebuf_shift(in, buf_shift);
2399 }
2400
2401 if (ret == TB_OK) {
2402 event->type = TB_EVENT_MOUSE;
2403 }
2404
2405 return ret;
2406 }
2407
2408 static int resize_cellbufs(void) {
2409 int rv;
2410 if_err_return(rv, cellbuf_resize(&global.back, global.width,
2411 global.height));
2412 if_err_return(rv, cellbuf_resize(&global.front, global.width,
2413 global.height));
2414 if_err_return(rv, cellbuf_clear(&global.front));
2415 if_err_return(rv, send_clear());
2416 return TB_OK;
2417 }
2418
2419 static void handle_resize(int sig) {
2420 int errno_copy = errno;
2421 write(global.resize_pipefd[1], &sig, sizeof(sig));
2422 errno = errno_copy;
2423 }
2424
2425 static int send_attr(uintattr_t fg, uintattr_t bg) {
2426 int rv;
2427 uintattr_t attr_bold, attr_blink, attr_italic,
2428 attr_underline, attr_reverse, attr_default;
2429 uintattr_t cfg, cbg;
2430
2431 if (fg == global.last_fg && bg == global.last_bg) {
2432 return TB_OK;
2433 }
2434
2435 if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]));
2436
2437 switch (global.output_mode) {
2438 default:
2439 case TB_OUTPUT_NORMAL:
2440 cfg = fg & 0x0f;
2441 cbg = bg & 0x0f;
2442 break;
2443
2444 case TB_OUTPUT_256:
2445 cfg = fg & 0xff;
2446 cbg = bg & 0xff;
2447 break;
2448
2449 case TB_OUTPUT_216:
2450 cfg = fg & 0xff;
2451 cbg = bg & 0xff;
2452 if (cfg > 216)
2453 cfg = 216;
2454 if (cbg > 216)
2455 cbg = 216;
2456 cfg += 0x0f;
2457 cbg += 0x0f;
2458 break;
2459
2460 case TB_OUTPUT_GRAYSCALE:
2461 cfg = fg & 0xff;
2462 cbg = bg & 0xff;
2463 if (cfg > 24)
2464 cfg = 24;
2465 if (cbg > 24)
2466 cbg = 24;
2467 cfg += 0xe7;
2468 cbg += 0xe7;
2469 break;
2470
2471 #ifdef TB_OPT_TRUECOLOR
2472 case TB_OUTPUT_TRUECOLOR:
2473 cfg = fg & 0xffffff;
2474 cbg = bg & 0xffffff;
2475 break;
2476 #endif
2477 }
2478
2479 #ifdef TB_OPT_TRUECOLOR
2480 if (global.output_mode == TB_OUTPUT_TRUECOLOR) {
2481 attr_bold = TB_TRUECOLOR_BOLD;
2482 attr_blink = TB_TRUECOLOR_BLINK;
2483 attr_italic = TB_TRUECOLOR_ITALIC;
2484 attr_underline = TB_TRUECOLOR_UNDERLINE;
2485 attr_reverse = TB_TRUECOLOR_REVERSE;
2486 attr_default = TB_TRUECOLOR_DEFAULT;
2487 } else
2488 #endif
2489 {
2490 attr_bold = TB_BOLD;
2491 attr_blink = TB_BLINK;
2492 attr_italic = TB_ITALIC;
2493 attr_underline = TB_UNDERLINE;
2494 attr_reverse = TB_REVERSE;
2495 attr_default = TB_DEFAULT;
2496 }
2497
2498 /* For convenience (and some back compat), interpret 0 as default in
2499 * some modes */
2500 if (global.output_mode == TB_OUTPUT_NORMAL ||
2501 global.output_mode == TB_OUTPUT_216 ||
2502 global.output_mode == TB_OUTPUT_GRAYSCALE)
2503 {
2504 if ((fg & 0xff) == 0)
2505 fg |= attr_default;
2506 if ((bg & 0xff) == 0)
2507 bg |= attr_default;
2508 }
2509
2510 if (fg & attr_bold)
2511 if_err_return(rv, bytebuf_puts(&global.out,
2512 global.caps[TB_CAP_BOLD]));
2513
2514 if (fg & attr_blink)
2515 if_err_return(rv, bytebuf_puts(&global.out,
2516 global.caps[TB_CAP_BLINK]));
2517
2518 if (fg & attr_underline)
2519 if_err_return(rv, bytebuf_puts(&global.out,
2520 global.caps[TB_CAP_UNDERLINE]));
2521
2522 if (fg & attr_italic)
2523 if_err_return(rv, bytebuf_puts(&global.out,
2524 global.caps[TB_CAP_ITALIC]));
2525
2526 if ((fg & attr_reverse) || (bg & attr_reverse))
2527 if_err_return(rv, bytebuf_puts(&global.out,
2528 global.caps[TB_CAP_REVERSE]));
2529
2530 if_err_return(rv, send_sgr(cfg, cbg, fg & attr_default,
2531 bg & attr_default));
2532
2533 global.last_fg = fg;
2534 global.last_bg = bg;
2535
2536 return TB_OK;
2537 }
2538
2539 static int send_sgr(uintattr_t cfg, uintattr_t cbg, uintattr_t fg_is_default,
2540 uintattr_t bg_is_default) {
2541 int rv;
2542 char nbuf[32];
2543
2544 if (fg_is_default && bg_is_default) {
2545 return TB_OK;
2546 }
2547
2548 switch (global.output_mode) {
2549 default:
2550 case TB_OUTPUT_NORMAL:
2551 send_literal(rv, "\x1b[");
2552 if (!fg_is_default) {
2553 send_literal(rv, "3");
2554 send_num(rv, nbuf, cfg - 1);
2555 if (!bg_is_default) {
2556 send_literal(rv, ";");
2557 }
2558 }
2559 if (!bg_is_default) {
2560 send_literal(rv, "4");
2561 send_num(rv, nbuf, cbg - 1);
2562 }
2563 send_literal(rv, "m");
2564 break;
2565
2566 case TB_OUTPUT_256:
2567 case TB_OUTPUT_216:
2568 case TB_OUTPUT_GRAYSCALE:
2569 send_literal(rv, "\x1b[");
2570 if (!fg_is_default) {
2571 send_literal(rv, "38;5;");
2572 send_num(rv, nbuf, cfg);
2573 if (!bg_is_default) {
2574 send_literal(rv, ";");
2575 }
2576 }
2577 if (!bg_is_default) {
2578 send_literal(rv, "48;5;");
2579 send_num(rv, nbuf, cbg);
2580 }
2581 send_literal(rv, "m");
2582 break;
2583
2584 #ifdef TB_OPT_TRUECOLOR
2585 case TB_OUTPUT_TRUECOLOR:
2586 send_literal(rv, "\x1b[");
2587 if (!fg_is_default) {
2588 send_literal(rv, "38;2;");
2589 send_num(rv, nbuf, (cfg >> 16) & 0xff);
2590 send_literal(rv, ";");
2591 send_num(rv, nbuf, (cfg >> 8) & 0xff);
2592 send_literal(rv, ";");
2593 send_num(rv, nbuf, cfg & 0xff);
2594 if (!bg_is_default) {
2595 send_literal(rv, ";");
2596 }
2597 }
2598 if (!bg_is_default) {
2599 send_literal(rv, "48;2;");
2600 send_num(rv, nbuf, (cbg >> 16) & 0xff);
2601 send_literal(rv, ";");
2602 send_num(rv, nbuf, (cbg >> 8) & 0xff);
2603 send_literal(rv, ";");
2604 send_num(rv, nbuf, cbg & 0xff);
2605 }
2606 send_literal(rv, "m");
2607 break;
2608 #endif
2609 }
2610 return TB_OK;
2611 }
2612
2613 static int send_cursor_if(int x, int y) {
2614 int rv;
2615 char nbuf[32];
2616 if (x < 0 || y < 0) {
2617 return TB_OK;
2618 }
2619 send_literal(rv, "\x1b[");
2620 send_num(rv, nbuf, y + 1);
2621 send_literal(rv, ";");
2622 send_num(rv, nbuf, x + 1);
2623 send_literal(rv, "H");
2624 return TB_OK;
2625 }
2626
2627 static int send_char(int x, int y, uint32_t ch) {
2628 return send_cluster(x, y, &ch, 1);
2629 }
2630
2631 static int send_cluster(int x, int y, uint32_t *ch, size_t nch) {
2632 int rv, i;
2633 char abuf[8];
2634
2635 if (global.last_x != x - 1 || global.last_y != y) {
2636 if_err_return(rv, send_cursor_if(x, y));
2637 }
2638 global.last_x = x;
2639 global.last_y = y;
2640
2641 for (i = 0; i < (int)nch; i++) {
2642 uint32_t ach = *(ch + i);
2643 int aw = tb_utf8_unicode_to_char(abuf, ach);
2644 if (!ach) {
2645 abuf[0] = ' ';
2646 }
2647 if_err_return(rv,
2648 bytebuf_nputs(&global.out, abuf, (size_t)aw));
2649 }
2650
2651 return TB_OK;
2652 }
2653
2654 static int convert_num(uint32_t num, char *buf) {
2655 int i, l = 0;
2656 char ch;
2657 do {
2658 /* '0' = 48; 48 + num%10 < 58 < MAX_8bitCHAR */
2659 buf[l++] = (char)('0' + (num % 10));
2660 num /= 10;
2661 } while (num);
2662 for (i = 0; i < l / 2; i++) {
2663 ch = buf[i];
2664 buf[i] = buf[l - 1 - i];
2665 buf[l - 1 - i] = ch;
2666 }
2667 return l;
2668 }
2669
2670 static int cell_cmp(struct tb_cell *a, struct tb_cell *b) {
2671 if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) {
2672 return 1;
2673 }
2674 #ifdef TB_OPT_EGC
2675 if (a->nech != b->nech) {
2676 return 1;
2677 } else if (a->nech > 0) { /* a->nech == b->nech */
2678 return memcmp(a->ech, b->ech, a->nech);
2679 }
2680 #endif
2681 return 0;
2682 }
2683
2684 static int cell_copy(struct tb_cell *dst, struct tb_cell *src) {
2685 #ifdef TB_OPT_EGC
2686 if (src->nech > 0) {
2687 return cell_set(dst, src->ech, src->nech, src->fg, src->bg);
2688 }
2689 #endif
2690 return cell_set(dst, &src->ch, 1, src->fg, src->bg);
2691 }
2692
2693 static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
2694 uintattr_t fg, uintattr_t bg) {
2695 cell->ch = ch ? *ch : 0;
2696 cell->fg = fg;
2697 cell->bg = bg;
2698 #ifdef TB_OPT_EGC
2699 if (nch <= 1) {
2700 cell->nech = 0;
2701 } else {
2702 int rv;
2703 if_err_return(rv, cell_reserve_ech(cell, nch + 1));
2704 memcpy(cell->ech, ch, nch);
2705 cell->ech[nch] = '\0';
2706 cell->nech = nch;
2707 }
2708 #else
2709 (void)nch;
2710 (void)cell_reserve_ech;
2711 #endif
2712 return TB_OK;
2713 }
2714
2715 static int cell_reserve_ech(struct tb_cell *cell, size_t n) {
2716 #ifdef TB_OPT_EGC
2717 if (cell->cech >= n) {
2718 return TB_OK;
2719 }
2720 if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) {
2721 return TB_ERR_MEM;
2722 }
2723 cell->cech = n;
2724 return TB_OK;
2725 #else
2726 (void)cell;
2727 (void)n;
2728 return TB_ERR;
2729 #endif
2730 }
2731
2732 static int cell_free(struct tb_cell *cell) {
2733 #ifdef TB_OPT_EGC
2734 if (cell->ech) {
2735 tb_free(cell->ech);
2736 }
2737 #endif
2738 memset(cell, 0, sizeof(*cell));
2739 return TB_OK;
2740 }
2741
2742 static int cellbuf_init(struct cellbuf_t *c, int w, int h) {
2743 c->cells = tb_malloc(sizeof(struct tb_cell) * w * h);
2744 if (!c->cells) {
2745 return TB_ERR_MEM;
2746 }
2747 memset(c->cells, 0, sizeof(struct tb_cell) * w * h);
2748 c->width = w;
2749 c->height = h;
2750 return TB_OK;
2751 }
2752
2753 static int cellbuf_free(struct cellbuf_t *c) {
2754 if (c->cells) {
2755 int i;
2756 for (i = 0; i < c->width * c->height; i++) {
2757 cell_free(&c->cells[i]);
2758 }
2759 tb_free(c->cells);
2760 }
2761 memset(c, 0, sizeof(*c));
2762 return TB_OK;
2763 }
2764
2765 static int cellbuf_clear(struct cellbuf_t *c) {
2766 int rv, i;
2767 uint32_t space = (uint32_t)' ';
2768 for (i = 0; i < c->width * c->height; i++) {
2769 if_err_return(rv, cell_set(
2770 &c->cells[i], &space, 1, global.fg, global.bg));
2771
2772 }
2773 return TB_OK;
2774 }
2775
2776 static int cellbuf_get(struct cellbuf_t *c, int x, int y,
2777 struct tb_cell **out) {
2778 if (x < 0 || x >= c->width || y < 0 || y >= c->height) {
2779 *out = NULL;
2780 return TB_ERR_OUT_OF_BOUNDS;
2781 }
2782 *out = &c->cells[(y * c->width) + x];
2783 return TB_OK;
2784 }
2785
2786 static int cellbuf_resize(struct cellbuf_t *c, int w, int h) {
2787 int rv;
2788
2789 int ow = c->width;
2790 int oh = c->height;
2791 int minw, minh, x, y;
2792 struct tb_cell *prev;
2793
2794 if (ow == w && oh == h) {
2795 return TB_OK;
2796 }
2797
2798 w = w < 1 ? 1 : w;
2799 h = h < 1 ? 1 : h;
2800
2801 minw = (w < ow) ? w : ow;
2802 minh = (h < oh) ? h : oh;
2803
2804 prev = c->cells;
2805
2806 if_err_return(rv, cellbuf_init(c, w, h));
2807 if_err_return(rv, cellbuf_clear(c));
2808
2809 x = 0;
2810 while (x < minw) {
2811 y = 0;
2812 while (y < minh) {
2813 struct tb_cell *src, *dst;
2814 src = &prev[(y * ow) + x];
2815 if_err_return(rv, cellbuf_get(c, x, y, &dst));
2816 if_err_return(rv, cell_copy(dst, src));
2817 y++;
2818 }
2819 x++;
2820 }
2821
2822 tb_free(prev);
2823
2824 return TB_OK;
2825 }
2826
2827 static int bytebuf_puts(struct bytebuf_t *b, const char *str) {
2828 return bytebuf_nputs(b, str, (size_t)strlen(str));
2829 }
2830
2831 static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) {
2832 int rv;
2833 if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1));
2834 memcpy(b->buf + b->len, str, nstr);
2835 b->len += nstr;
2836 b->buf[b->len] = '\0';
2837 return TB_OK;
2838 }
2839
2840 static int bytebuf_shift(struct bytebuf_t *b, size_t n) {
2841 size_t nmove;
2842 if (n > b->len) {
2843 n = b->len;
2844 }
2845 nmove = b->len - n;
2846 memmove(b->buf, b->buf + n, nmove);
2847 b->len -= n;
2848 return TB_OK;
2849 }
2850
2851 static int bytebuf_flush(struct bytebuf_t *b, int fd) {
2852 ssize_t write_rv;
2853 if (b->len <= 0) {
2854 return TB_OK;
2855 }
2856 write_rv = write(fd, b->buf, b->len);
2857 if (write_rv < 0 || (size_t)write_rv != b->len) {
2858 /* Note, errno will be 0 on partial write */
2859 global.last_errno = errno;
2860 return TB_ERR;
2861 }
2862 b->len = 0;
2863 return TB_OK;
2864 }
2865
2866 static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) {
2867 char *newbuf;
2868 size_t newcap;
2869
2870 if (b->cap >= sz) {
2871 return TB_OK;
2872 }
2873 newcap = b->cap > 0 ? b->cap : 1;
2874 while (newcap < sz) {
2875 newcap *= 2;
2876 }
2877 if (b->buf) {
2878 newbuf = tb_realloc(b->buf, newcap);
2879 } else {
2880 newbuf = tb_malloc(newcap);
2881 }
2882 if (!newbuf) {
2883 return TB_ERR_MEM;
2884 }
2885 b->buf = newbuf;
2886 b->cap = newcap;
2887 return TB_OK;
2888 }
2889
2890 static int bytebuf_free(struct bytebuf_t *b) {
2891 if (b->buf) {
2892 tb_free(b->buf);
2893 }
2894 memset(b, 0, sizeof(*b));
2895 return TB_OK;
2896 }
2897