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