💾 Archived View for gemini.rmf-dev.com › repo › Vaati › mz › files › 2d21324ddbd7d1d5a8ba35d199387ec… captured on 2023-05-24 at 18:40:21. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
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 const char *retry_with = "xterm";
950
951 tb_reset();
952 global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1;
953 global.rfd = rfd;
954 global.wfd = wfd;
955
956 term = getenv("TERM");
957 if (!term) {
958 term = retry_with;
959 setenv("TERM", term, 0);
960 }
961
962 if (strcmp("st-256color", term) == 0) {
963 setenv("TERM", "screen-256color", 1);
964 } else if (strcmp("st", term) == 0) {
965 setenv("TERM", "screen", 1);
966 }
967
968 do {
969 retry:
970 if_err_break(rv, init_term_attrs());
971 if_err_break(rv, init_term_caps());
972 if_err_break(rv, init_cap_trie());
973 if_err_break(rv, init_resize_handler());
974 if_err_break(rv, send_init_escape_codes());
975 if_err_break(rv, send_clear());
976 if_err_break(rv, update_term_size());
977 if_err_break(rv, init_cellbuf());
978 global.initialized = 1;
979 } while (0);
980
981 if (rv != TB_OK) {
982 if (strcmp(term, retry_with)) {
983 setenv("TERM", retry_with, 1);
984 term = retry_with;
985 goto retry;
986 }
987 tb_deinit();
988 }
989
990 return rv;
991 }
992
993 int tb_shutdown(void) {
994 if_not_init_return();
995 tb_deinit();
996 return TB_OK;
997 }
998
999 int tb_width(void) {
1000 if_not_init_return();
1001 return global.width;
1002 }
1003
1004 int tb_height(void) {
1005 if_not_init_return();
1006 return global.height;
1007 }
1008
1009 int tb_clear(void) {
1010 if_not_init_return();
1011 return cellbuf_clear(&global.back);
1012 }
1013
1014 int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) {
1015 if_not_init_return();
1016 global.fg = fg;
1017 global.bg = bg;
1018 return TB_OK;
1019 }
1020
1021 int tb_present(void) {
1022
1023 int rv, x, y, i;
1024
1025 if_not_init_return();
1026
1027 global.last_x = -1;
1028 global.last_y = -1;
1029
1030 for (y = 0; y < global.front.height; y++) {
1031 for (x = 0; x < global.front.width;) {
1032 struct tb_cell *back, *front;
1033 int w;
1034 if_err_return(rv, cellbuf_get(
1035 &global.back, x, y, &back));
1036 if_err_return(rv, cellbuf_get(
1037 &global.front, x, y, &front));
1038
1039 {
1040 #ifdef TB_OPT_EGC
1041 if (back->nech > 0)
1042 w = wcswidth((wchar_t *)back->ech, back->nech);
1043 else
1044 #endif
1045 /* wcwidth() simply returns -1 on overflow of
1046 * wchar_t */
1047 w = wcwidth((wchar_t)back->ch);
1048 }
1049 if (w < 1) {
1050 w = 1;
1051 }
1052
1053 if (!cell_cmp(back, front)) {
1054 x += w;
1055 continue;
1056 }
1057 cell_copy(front, back);
1058
1059 send_attr(back->fg, back->bg);
1060 if (w > 1 && x >= global.front.width - (w - 1)) {
1061 for (i = x; i < global.front.width; i++) {
1062 send_char(i, y, ' ');
1063 }
1064 } else {
1065 {
1066 #ifdef TB_OPT_EGC
1067 if (back->nech > 0)
1068 send_cluster(x, y, back->ech,
1069 back->nech);
1070 else
1071 #endif
1072 send_char(x, y, back->ch);
1073 }
1074 for (i = 1; i < w; i++) {
1075 struct tb_cell *front_wide;
1076 if_err_return(rv, cellbuf_get(
1077 &global.front, x + i, y,
1078 &front_wide));
1079 if_err_return(rv, cell_set(front_wide,
1080 0, 1, back->fg, back->bg));
1081
1082 }
1083 }
1084 x += w;
1085 }
1086 }
1087
1088 if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
1089 if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
1090
1091 return TB_OK;
1092 }
1093
1094 int tb_set_cursor(int cx, int cy) {
1095 int rv;
1096 if_not_init_return();
1097 if (cx < 0)
1098 cx = 0;
1099 if (cy < 0)
1100 cy = 0;
1101 if (global.cursor_x == -1) {
1102 if_err_return(rv, bytebuf_puts(&global.out,
1103 global.caps[TB_CAP_SHOW_CURSOR]));
1104 }
1105 if_err_return(rv, send_cursor_if(cx, cy));
1106 global.cursor_x = cx;
1107 global.cursor_y = cy;
1108 return TB_OK;
1109 }
1110
1111 int tb_hide_cursor(void) {
1112 int rv;
1113 if_not_init_return();
1114 if (global.cursor_x >= 0) {
1115 if_err_return(rv, bytebuf_puts(&global.out,
1116 global.caps[TB_CAP_HIDE_CURSOR]));
1117 }
1118 global.cursor_x = -1;
1119 global.cursor_y = -1;
1120 return TB_OK;
1121 }
1122
1123 int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) {
1124 if_not_init_return();
1125 return tb_set_cell_ex(x, y, &ch, 1, fg, bg);
1126 }
1127
1128 int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
1129 uintattr_t bg) {
1130 int rv;
1131 struct tb_cell *cell;
1132 if_not_init_return();
1133 if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1134 if_err_return(rv, cell_set(cell, ch, nch, fg, bg));
1135 return TB_OK;
1136 }
1137
1138 int tb_extend_cell(int x, int y, uint32_t ch) {
1139 if_not_init_return();
1140 #ifdef TB_OPT_EGC
1141 int rv;
1142 struct tb_cell *cell;
1143 size_t nech;
1144 if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1145 if (cell->nech > 0) { /* append to ech */
1146 nech = cell->nech + 1;
1147 if_err_return(rv, cell_reserve_ech(cell, nech));
1148 cell->ech[nech - 1] = ch;
1149 } else { /* make new ech */
1150 nech = 2;
1151 if_err_return(rv, cell_reserve_ech(cell, nech));
1152 cell->ech[0] = cell->ch;
1153 cell->ech[1] = ch;
1154 }
1155 cell->ech[nech] = '\0';
1156 cell->nech = nech;
1157 return TB_OK;
1158 #else
1159 (void)x;
1160 (void)y;
1161 (void)ch;
1162 return TB_ERR;
1163 #endif
1164 }
1165
1166 int tb_set_input_mode(int mode) {
1167 if_not_init_return();
1168 if (mode == TB_INPUT_CURRENT) {
1169 return global.input_mode;
1170 }
1171
1172 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) {
1173 mode |= TB_INPUT_ESC;
1174 }
1175
1176 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) ==
1177 (TB_INPUT_ESC | TB_INPUT_ALT)) {
1178 mode &= ~TB_INPUT_ALT;
1179 }
1180
1181 if (mode & TB_INPUT_MOUSE) {
1182 bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE);
1183 bytebuf_flush(&global.out, global.wfd);
1184 } else {
1185 bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
1186 bytebuf_flush(&global.out, global.wfd);
1187 }
1188
1189 global.input_mode = mode;
1190 return TB_OK;
1191 }
1192
1193 int tb_set_output_mode(int mode) {
1194 if_not_init_return();
1195 switch (mode) {
1196 case TB_OUTPUT_CURRENT:
1197 return global.output_mode;
1198 case TB_OUTPUT_NORMAL:
1199 case TB_OUTPUT_256:
1200 case TB_OUTPUT_216:
1201 case TB_OUTPUT_GRAYSCALE:
1202 #ifdef TB_OPT_TRUECOLOR
1203 case TB_OUTPUT_TRUECOLOR:
1204 #endif
1205 global.output_mode = mode;
1206 return TB_OK;
1207 }
1208 return TB_ERR;
1209 }
1210
1211 int tb_peek_event(struct tb_event *event, int timeout_ms) {
1212 if_not_init_return();
1213 return wait_event(event, timeout_ms);
1214 }
1215
1216 int tb_poll_event(struct tb_event *event) {
1217 if_not_init_return();
1218 return wait_event(event, -1);
1219 }
1220
1221 int tb_get_fds(int *ttyfd, int *resizefd) {
1222 if_not_init_return();
1223
1224 *ttyfd = global.rfd;
1225 *resizefd = global.resize_pipefd[0];
1226
1227 return TB_OK;
1228 }
1229
1230 int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) {
1231 return tb_print_ex(x, y, fg, bg, NULL, str);
1232 }
1233
1234 int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1235 const char *str) {
1236 int rv;
1237 uint32_t uni;
1238 int w, ix = x;
1239 if (out_w) {
1240 *out_w = 0;
1241 }
1242 while (*str) {
1243 str += tb_utf8_char_to_unicode(&uni, str);
1244 w = wcwidth((wchar_t)uni);
1245 if (w <= 0) {
1246 w = 1;
1247 }
1248 if (w == 0 && x > ix) {
1249 if_err_return(rv, tb_extend_cell(x - 1, y, uni));
1250 } else {
1251 if_err_return(rv, tb_set_cell(x, y, uni, fg, bg));
1252 }
1253 x += w;
1254 if (out_w) {
1255 *out_w += w;
1256 }
1257 }
1258 return TB_OK;
1259 }
1260
1261 int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt,
1262 ...) {
1263 int rv;
1264 va_list vl;
1265 va_start(vl, fmt);
1266 rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl);
1267 va_end(vl);
1268 return rv;
1269 }
1270
1271 int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1272 const char *fmt, ...) {
1273 int rv;
1274 va_list vl;
1275 va_start(vl, fmt);
1276 rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl);
1277 va_end(vl);
1278 return rv;
1279 }
1280
1281 int tb_send(const char *buf, size_t nbuf) {
1282 return bytebuf_nputs(&global.out, buf, nbuf);
1283 }
1284
1285 int tb_sendf(const char *fmt, ...) {
1286 int rv;
1287 char buf[TB_OPT_PRINTF_BUF];
1288 va_list vl;
1289 va_start(vl, fmt);
1290 rv = vsnprintf(buf, sizeof(buf), fmt, vl);
1291 va_end(vl);
1292 if (rv < 0 || rv >= (int)sizeof(buf)) {
1293 return TB_ERR;
1294 }
1295 return tb_send(buf, (size_t)rv);
1296 }
1297
1298 int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) {
1299 switch (fn_type) {
1300 case TB_FUNC_EXTRACT_PRE:
1301 global.fn_extract_esc_pre = fn;
1302 return TB_OK;
1303 case TB_FUNC_EXTRACT_POST:
1304 global.fn_extract_esc_post = fn;
1305 return TB_OK;
1306 }
1307 return TB_ERR;
1308 }
1309
1310 struct tb_cell *tb_cell_buffer(void) {
1311 if (!global.initialized)
1312 return NULL;
1313 return global.back.cells;
1314 }
1315
1316 int tb_utf8_char_length(char c) {
1317 return utf8_length[(unsigned char)c];
1318 }
1319
1320 int tb_utf8_char_to_unicode(uint32_t *out, const char *c) {
1321
1322 int i;
1323 unsigned char len, mask;
1324 uint32_t result;
1325
1326 if (*c == 0) {
1327 return TB_ERR;
1328 }
1329
1330 len = tb_utf8_char_length(*c);
1331 mask = utf8_mask[len - 1];
1332 result = c[0] & mask;
1333 for (i = 1; i < len; ++i) {
1334 result <<= 6;
1335 result |= c[i] & 0x3f;
1336 }
1337
1338 *out = result;
1339 return (int)len;
1340 }
1341
1342 int tb_utf8_unicode_to_char(char *out, uint32_t c) {
1343 int len = 0;
1344 int first;
1345 int i;
1346
1347 if (c < 0x80) {
1348 first = 0;
1349 len = 1;
1350 } else if (c < 0x800) {
1351 first = 0xc0;
1352 len = 2;
1353 } else if (c < 0x10000) {
1354 first = 0xe0;
1355 len = 3;
1356 } else if (c < 0x200000) {
1357 first = 0xf0;
1358 len = 4;
1359 } else if (c < 0x4000000) {
1360 first = 0xf8;
1361 len = 5;
1362 } else {
1363 first = 0xfc;
1364 len = 6;
1365 }
1366
1367 for (i = len - 1; i > 0; --i) {
1368 out[i] = (c & 0x3f) | 0x80;
1369 c >>= 6;
1370 }
1371 out[0] = c | first;
1372
1373 return len;
1374 }
1375
1376 int tb_last_errno(void) {
1377 return global.last_errno;
1378 }
1379
1380 const char *tb_strerror(int err) {
1381 switch (err) {
1382 case TB_OK:
1383 return "Success";
1384 case TB_ERR_NEED_MORE:
1385 return "Not enough input";
1386 case TB_ERR_INIT_ALREADY:
1387 return "Termbox initialized already";
1388 case TB_ERR_MEM:
1389 return "Out of memory";
1390 case TB_ERR_NO_EVENT:
1391 return "No event";
1392 case TB_ERR_NO_TERM:
1393 return "No TERM in environment";
1394 case TB_ERR_NOT_INIT:
1395 return "Termbox not initialized";
1396 case TB_ERR_OUT_OF_BOUNDS:
1397 return "Out of bounds";
1398 case TB_ERR_UNSUPPORTED_TERM:
1399 return "Unsupported terminal";
1400 case TB_ERR_CAP_COLLISION:
1401 return "Termcaps collision";
1402 case TB_ERR_RESIZE_SSCANF:
1403 return "Terminal width/height not received by sscanf()"
1404 " after resize";
1405 case TB_ERR:
1406 case TB_ERR_INIT_OPEN:
1407 case TB_ERR_READ:
1408 case TB_ERR_RESIZE_IOCTL:
1409 case TB_ERR_RESIZE_PIPE:
1410 case TB_ERR_RESIZE_SIGACTION:
1411 case TB_ERR_POLL:
1412 case TB_ERR_TCGETATTR:
1413 case TB_ERR_TCSETATTR:
1414 case TB_ERR_RESIZE_WRITE:
1415 case TB_ERR_RESIZE_POLL:
1416 case TB_ERR_RESIZE_READ:
1417 default:
1418 strerror_r(global.last_errno, global.errbuf,
1419 sizeof(global.errbuf));
1420 return (const char *)global.errbuf;
1421 }
1422 }
1423
1424 int tb_has_truecolor(void) {
1425 #ifdef TB_OPT_TRUECOLOR
1426 return 1;
1427 #else
1428 return 0;
1429 #endif
1430 }
1431
1432 int tb_has_egc(void) {
1433 #ifdef TB_OPT_EGC
1434 return 1;
1435 #else
1436 return 0;
1437 #endif
1438 }
1439
1440 const char *tb_version(void) {
1441 return TB_VERSION_STR;
1442 }
1443
1444 static int tb_reset(void) {
1445 int ttyfd_open = global.ttyfd_open;
1446 memset(&global, 0, sizeof(global));
1447 global.ttyfd = -1;
1448 global.rfd = -1;
1449 global.wfd = -1;
1450 global.ttyfd_open = ttyfd_open;
1451 global.resize_pipefd[0] = -1;
1452 global.resize_pipefd[1] = -1;
1453 global.width = -1;
1454 global.height = -1;
1455 global.cursor_x = -1;
1456 global.cursor_y = -1;
1457 global.last_x = -1;
1458 global.last_y = -1;
1459 global.fg = TB_DEFAULT;
1460 global.bg = TB_DEFAULT;
1461 global.last_fg = ~global.fg;
1462 global.last_bg = ~global.bg;
1463 global.input_mode = TB_INPUT_ESC;
1464 global.output_mode = TB_OUTPUT_NORMAL;
1465 return TB_OK;
1466 }
1467
1468 static int init_term_attrs(void) {
1469
1470 struct termios tios;
1471
1472 if (global.ttyfd < 0) {
1473 return TB_OK;
1474 }
1475
1476 if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) {
1477 global.last_errno = errno;
1478 return TB_ERR_TCGETATTR;
1479 }
1480
1481 memcpy(&tios, &global.orig_tios, sizeof(tios));
1482 global.has_orig_tios = 1;
1483
1484 cfmakeraw(&tios);
1485 tios.c_cc[VMIN] = 1;
1486 tios.c_cc[VTIME] = 0;
1487
1488 if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) {
1489 global.last_errno = errno;
1490 return TB_ERR_TCSETATTR;
1491 }
1492
1493 return TB_OK;
1494 }
1495
1496 int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1497 const char *fmt, va_list vl) {
1498 int rv;
1499 char buf[TB_OPT_PRINTF_BUF];
1500 rv = vsnprintf(buf, sizeof(buf), fmt, vl);
1501 if (rv < 0 || rv >= (int)sizeof(buf)) {
1502 return TB_ERR;
1503 }
1504 return tb_print_ex(x, y, fg, bg, out_w, buf);
1505 }
1506
1507 static int init_term_caps(void) {
1508 if (load_terminfo() == TB_OK) {
1509 return parse_terminfo_caps();
1510 }
1511 return load_builtin_caps();
1512 }
1513
1514 static int init_cap_trie(void) {
1515 int rv, i;
1516
1517 /* Add caps from terminfo or built-in */
1518 for (i = 0; i < TB_CAP__COUNT_KEYS; i++) {
1519 if_err_return(rv,
1520 cap_trie_add(global.caps[i], tb_key_i(i), 0));
1521 }
1522
1523 /* Add built-in mod caps */
1524 for (i = 0; builtin_mod_caps[i].cap != NULL; i++) {
1525 rv = cap_trie_add(builtin_mod_caps[i].cap,
1526 builtin_mod_caps[i].key,
1527 builtin_mod_caps[i].mod);
1528 /*
1529 * Collisions are OK. This can happen if global.caps collides
1530 * with builtin_mod_caps. It is desirable to give precedence to
1531 * global.caps here.
1532 */
1533 if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) {
1534 return rv;
1535 }
1536 }
1537
1538 return TB_OK;
1539 }
1540
1541 static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) {
1542 struct cap_trie_t *next, *node = &global.cap_trie;
1543 size_t i, j;
1544 for (i = 0; cap[i] != '\0'; i++) {
1545 char c = cap[i];
1546 next = NULL;
1547
1548 /* Check if c is already a child of node */
1549 for (j = 0; j < node->nchildren; j++) {
1550 if (node->children[j].c == c) {
1551 next = &node->children[j];
1552 break;
1553 }
1554 }
1555 if (!next) {
1556 /* We need to add a new child to node */
1557 node->nchildren += 1;
1558 node->children = tb_realloc(node->children,
1559 sizeof(*node) * node->nchildren);
1560 if (!node->children) {
1561 return TB_ERR_MEM;
1562 }
1563 next = &node->children[node->nchildren - 1];
1564 memset(next, 0, sizeof(*next));
1565 next->c = c;
1566 }
1567
1568 /* Continue */
1569 node = next;
1570 }
1571
1572 if (node->is_leaf) {
1573 /* Already a leaf here */
1574 return TB_ERR_CAP_COLLISION;
1575 }
1576
1577 node->is_leaf = 1;
1578 node->key = key;
1579 node->mod = mod;
1580 return TB_OK;
1581 }
1582
1583 static int cap_trie_find(const char *buf, size_t nbuf,
1584 struct cap_trie_t **last, size_t *depth) {
1585 struct cap_trie_t *next, *node = &global.cap_trie;
1586 size_t i, j;
1587 *last = node;
1588 *depth = 0;
1589 for (i = 0; i < nbuf; i++) {
1590 char c = buf[i];
1591 next = NULL;
1592
1593 /* Find c in node.children */
1594 for (j = 0; j < node->nchildren; j++) {
1595 if (node->children[j].c == c) {
1596 next = &node->children[j];
1597 break;
1598 }
1599 }
1600 if (!next) {
1601 /* Not found */
1602 return TB_OK;
1603 }
1604 node = next;
1605 *last = node;
1606 *depth += 1;
1607 if (node->is_leaf && node->nchildren < 1) {
1608 break;
1609 }
1610 }
1611 return TB_OK;
1612 }
1613
1614 static int cap_trie_deinit(struct cap_trie_t *node) {
1615 size_t j;
1616 for (j = 0; j < node->nchildren; j++) {
1617 cap_trie_deinit(&node->children[j]);
1618 }
1619 if (node->children) {
1620 tb_free(node->children);
1621 }
1622 memset(node, 0, sizeof(*node));
1623 return TB_OK;
1624 }
1625
1626 static int init_resize_handler(void) {
1627 struct sigaction sa;
1628 if (pipe(global.resize_pipefd) != 0) {
1629 global.last_errno = errno;
1630 return TB_ERR_RESIZE_PIPE;
1631 }
1632
1633 memset(&sa, 0, sizeof(sa));
1634 sa.sa_handler = handle_resize;
1635 if (sigaction(SIGWINCH, &sa, NULL) != 0) {
1636 global.last_errno = errno;
1637 return TB_ERR_RESIZE_SIGACTION;
1638 }
1639
1640 return TB_OK;
1641 }
1642
1643 static int send_init_escape_codes(void) {
1644 int rv;
1645 if_err_return(rv, bytebuf_puts(&global.out,
1646 global.caps[TB_CAP_ENTER_CA]));
1647 if_err_return(rv, bytebuf_puts(&global.out,
1648 global.caps[TB_CAP_ENTER_KEYPAD]));
1649 if_err_return(rv, bytebuf_puts(&global.out,
1650 global.caps[TB_CAP_HIDE_CURSOR]));
1651 return TB_OK;
1652 }
1653
1654 static int send_clear(void) {
1655 int rv;
1656
1657 if_err_return(rv, send_attr(global.fg, global.bg));
1658 if_err_return(rv, bytebuf_puts(&global.out,
1659 global.caps[TB_CAP_CLEAR_SCREEN]));
1660
1661 if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
1662 if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
1663
1664 global.last_x = -1;
1665 global.last_y = -1;
1666
1667 return TB_OK;
1668 }
1669
1670 static int update_term_size(void) {
1671 int rv, ioctl_errno;
1672 struct winsize sz;
1673
1674 if (global.ttyfd < 0) {
1675 return TB_OK;
1676 }
1677
1678 memset(&sz, 0, sizeof(sz));
1679
1680 /* Try ioctl TIOCGWINSZ */
1681 if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) {
1682 global.width = sz.ws_col;
1683 global.height = sz.ws_row;
1684 return TB_OK;
1685 }
1686 ioctl_errno = errno;
1687
1688 /* Try >cursor(9999,9999), >u7, <u6 */
1689 if_ok_return(rv, update_term_size_via_esc());
1690
1691 global.last_errno = ioctl_errno;
1692 return TB_ERR_RESIZE_IOCTL;
1693 }
1694
1695 static int update_term_size_via_esc(void) {
1696 #ifndef TB_RESIZE_FALLBACK_MS
1697 #define TB_RESIZE_FALLBACK_MS 1000
1698 #endif
1699
1700 char *move_and_report = "\x1b[9999;9999H\x1b[6n";
1701 int rw, rh;
1702 char buf[TB_OPT_READ_BUF];
1703 ssize_t write_rv, read_rv;
1704 int select_rv;
1705 struct timeval timeout;
1706 fd_set fds;
1707
1708 write_rv = write(global.wfd, move_and_report, strlen(move_and_report));
1709 if (write_rv != (ssize_t)strlen(move_and_report)) {
1710 return TB_ERR_RESIZE_WRITE;
1711 }
1712
1713 FD_ZERO(&fds);
1714 FD_SET(global.rfd, &fds);
1715
1716 timeout.tv_sec = 0;
1717 timeout.tv_usec = TB_RESIZE_FALLBACK_MS * 1000;
1718
1719 select_rv = select(global.rfd + 1, &fds, NULL, NULL, &timeout);
1720
1721 if (select_rv != 1) {
1722 global.last_errno = errno;
1723 return TB_ERR_RESIZE_POLL;
1724 }
1725
1726 read_rv = read(global.rfd, buf, sizeof(buf) - 1);
1727 if (read_rv < 1) {
1728 global.last_errno = errno;
1729 return TB_ERR_RESIZE_READ;
1730 }
1731 buf[read_rv] = '\0';
1732
1733 if (sscanf(buf, "\x1b[%d;%dR", &rh, &rw) != 2) {
1734 return TB_ERR_RESIZE_SSCANF;
1735 }
1736
1737 global.width = rw;
1738 global.height = rh;
1739 return TB_OK;
1740 }
1741
1742 static int init_cellbuf(void) {
1743 int rv;
1744 if_err_return(rv, cellbuf_init(&global.back, global.width,
1745 global.height));
1746 if_err_return(rv, cellbuf_init(&global.front, global.width,
1747 global.height));
1748 if_err_return(rv, cellbuf_clear(&global.back));
1749 if_err_return(rv, cellbuf_clear(&global.front));
1750 return TB_OK;
1751 }
1752
1753 static int tb_deinit(void) {
1754
1755 struct sigaction sig = {0};
1756
1757 if (global.caps[0] != NULL && global.wfd >= 0) {
1758 bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
1759 bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]);
1760 bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]);
1761 bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]);
1762 bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]);
1763 bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
1764 bytebuf_flush(&global.out, global.wfd);
1765 }
1766 if (global.ttyfd >= 0) {
1767 if (global.has_orig_tios) {
1768 tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios);
1769 }
1770 if (global.ttyfd_open) {
1771 close(global.ttyfd);
1772 global.ttyfd_open = 0;
1773 }
1774 }
1775
1776 sig.sa_handler = SIG_DFL;
1777 sigaction(SIGWINCH, &sig, NULL);
1778 if (global.resize_pipefd[0] >= 0)
1779 close(global.resize_pipefd[0]);
1780 if (global.resize_pipefd[1] >= 0)
1781 close(global.resize_pipefd[1]);
1782
1783 cellbuf_free(&global.back);
1784 cellbuf_free(&global.front);
1785 bytebuf_free(&global.in);
1786 bytebuf_free(&global.out);
1787
1788 if (global.terminfo)
1789 tb_free(global.terminfo);
1790
1791 cap_trie_deinit(&global.cap_trie);
1792
1793 tb_reset();
1794 return TB_OK;
1795 }
1796
1797 static int load_terminfo(void) {
1798 int rv;
1799 char tmp[PATH_MAX];
1800 const char *term, *terminfo, *home, *dirs;
1801
1802 /*
1803 * See terminfo(5) "Fetching Compiled Descriptions" for a description
1804 * of this behavior. Some of these paths are compile-time ncurses
1805 * options, so best guesses are used here.
1806 */
1807 term = getenv("TERM");
1808 if (!term) {
1809 return TB_ERR;
1810 }
1811
1812 /* If TERMINFO is set, try that directory and stop */
1813 terminfo = getenv("TERMINFO");
1814 if (terminfo) {
1815 return load_terminfo_from_path(terminfo, term);
1816 }
1817
1818 /* Next try ~/.terminfo */
1819 home = getenv("HOME");
1820 if (home) {
1821 if_err_return(rv, snprintf_(tmp, sizeof(tmp), "%s/.terminfo",
1822 home));
1823 if_ok_return(rv, load_terminfo_from_path(tmp, term));
1824 }
1825
1826 /*
1827 * Next try TERMINFO_DIRS
1828 *
1829 * Note, empty entries are supposed to be interpretted as the
1830 * "compiled-in default", which is of course system-dependent.
1831 * Previously /etc/terminfo was used here. Let's skip empty entries
1832 * altogether rather than give precedence to a guess, and check common
1833 * paths after this loop.
1834 */
1835 dirs = getenv("TERMINFO_DIRS");
1836 if (dirs) {
1837 char *dir;
1838 if_err_return(rv, snprintf_(tmp, sizeof(tmp), "%s", dirs));
1839 dir = strtok(tmp, ":");
1840 while (dir) {
1841 const char *cdir = dir;
1842 if (*cdir != '\0') {
1843 if_ok_return(rv,
1844 load_terminfo_from_path(cdir, term));
1845 }
1846 dir = strtok(NULL, ":");
1847 }
1848 }
1849
1850 #ifdef TB_TERMINFO_DIR
1851 if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term));
1852 #endif
1853 if_ok_return(rv,
1854 load_terminfo_from_path("/usr/local/etc/terminfo", term));
1855 if_ok_return(rv,
1856 load_terminfo_from_path("/usr/local/share/terminfo", term));
1857 if_ok_return(rv,
1858 load_terminfo_from_path("/usr/local/lib/terminfo", term));
1859 if_ok_return(rv,
1860 load_terminfo_from_path("/etc/terminfo", term));
1861 if_ok_return(rv,
1862 load_terminfo_from_path("/usr/share/terminfo", term));
1863 if_ok_return(rv,
1864 load_terminfo_from_path("/usr/lib/terminfo", term));
1865 if_ok_return(rv,
1866 load_terminfo_from_path("/usr/share/lib/terminfo", term));
1867 if_ok_return(rv,
1868 load_terminfo_from_path("/lib/terminfo", term));
1869
1870 return TB_ERR;
1871 }
1872
1873 static int load_terminfo_from_path(const char *path, const char *term) {
1874 char tmp[PATH_MAX];
1875 int rv;
1876
1877 /* Look for term at this terminfo location, e.g., <terminfo>/x/xterm */
1878 if_err_return(rv, snprintf_(tmp, sizeof(tmp),
1879 "%s/%c/%s", path, term[0], term));
1880 if_ok_return(rv, read_terminfo_path(tmp));
1881
1882 #ifdef __APPLE__
1883 /* Try the Darwin equivalent path, e.g., <terminfo>/78/xterm */
1884 if_err_return(rv, snprintf_(tmp, sizeof(tmp),
1885 "%s/%x/%s", path, term[0], term));
1886 return read_terminfo_path(tmp);
1887 #endif
1888
1889 return TB_ERR;
1890 }
1891
1892 static int read_terminfo_path(const char *path) {
1893 size_t fsize;
1894 char *data;
1895 struct stat st;
1896 FILE *fp = fopen(path, "rb");
1897 if (!fp) {
1898 return TB_ERR;
1899 }
1900
1901 if (fstat(fileno(fp), &st) != 0) {
1902 fclose(fp);
1903 return TB_ERR;
1904 }
1905
1906 fsize = st.st_size;
1907 data = tb_malloc(fsize);
1908 if (!data) {
1909 fclose(fp);
1910 return TB_ERR;
1911 }
1912
1913 if (fread(data, 1, fsize, fp) != fsize) {
1914 fclose(fp);
1915 tb_free(data);
1916 return TB_ERR;
1917 }
1918
1919 global.terminfo = data;
1920 global.nterminfo = fsize;
1921
1922 fclose(fp);
1923 return TB_OK;
1924 }
1925
1926 static int parse_terminfo_caps(void) {
1927 /*
1928 * See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT"
1929 * for a description of this behavior.
1930 */
1931 int16_t *header;
1932 int bytes_per_int, align_offset, pos_str_offsets, pos_str_table, i;
1933
1934 /* Ensure there's at least a header's worth of data */
1935 if (global.nterminfo < 6) {
1936 return TB_ERR;
1937 }
1938
1939 header = (int16_t *)global.terminfo;
1940 /*
1941 * header[0] the magic number (octal 0432 or 01036)
1942 * header[1] the size, in bytes, of the names section
1943 * header[2] the number of bytes in the boolean section
1944 * header[3] the number of short integers in the numbers section
1945 * header[4] the number of offsets in the strings section
1946 * header[5] the size, in bytes, of the string table
1947 */
1948
1949 /* Legacy ints are 16-bit, extended ints are 32-bit */
1950 bytes_per_int = header[0] == 01036 ? 4 /* 32-bit */
1951 : 2; /* 16-bit */
1952
1953 /*
1954 * Between the boolean section and the number section, a null byte
1955 * will be inserted, if necessary, to ensure that the number section
1956 * begins on an even byte
1957 */
1958 align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0;
1959
1960 pos_str_offsets =
1961 (6 * sizeof(int16_t)) /* header (12 bytes) */
1962 + header[1] /* length of names section */
1963 + header[2] /* length of boolean section */
1964 + align_offset +
1965 (header[3] * bytes_per_int); /* length of numbers section */
1966
1967 /* length of string offsets table */
1968 pos_str_table = pos_str_offsets + (header[4] * sizeof(int16_t));
1969
1970 /* Load caps */
1971 for (i = 0; i < TB_CAP__COUNT; i++) {
1972 const char *cap = get_terminfo_string(pos_str_offsets,
1973 pos_str_table, header[5],
1974 terminfo_cap_indexes[i]);
1975 if (!cap) {
1976 /* Something is not right */
1977 return TB_ERR;
1978 }
1979 global.caps[i] = cap;
1980 }
1981
1982 return TB_OK;
1983 }
1984
1985 static int load_builtin_caps(void) {
1986 int i, j;
1987 const char *term = getenv("TERM");
1988
1989 if (!term) {
1990 return TB_ERR_NO_TERM;
1991 }
1992
1993 /* Check for exact TERM match */
1994 for (i = 0; builtin_terms[i].name != NULL; i++) {
1995 if (strcmp(term, builtin_terms[i].name) == 0) {
1996 for (j = 0; j < TB_CAP__COUNT; j++) {
1997 global.caps[j] = builtin_terms[i].caps[j];
1998 }
1999 return TB_OK;
2000 }
2001 }
2002
2003 /* Check for partial TERM or alias match */
2004 for (i = 0; builtin_terms[i].name != NULL; i++) {
2005 if (strstr(term, builtin_terms[i].name) != NULL ||
2006 (*(builtin_terms[i].alias) != '\0' &&
2007 strstr(term, builtin_terms[i].alias) != NULL))
2008 {
2009 for (j = 0; j < TB_CAP__COUNT; j++) {
2010 global.caps[j] = builtin_terms[i].caps[j];
2011 }
2012 return TB_OK;
2013 }
2014 }
2015
2016 return TB_ERR_UNSUPPORTED_TERM;
2017 }
2018
2019 static const char *get_terminfo_string(int16_t str_offsets_pos,
2020 int16_t str_table_pos, int16_t str_table_len,
2021 int16_t str_index) {
2022 const int16_t *str_offset =
2023 (int16_t *)(global.terminfo + (int)str_offsets_pos +
2024 ((int)str_index * (int)sizeof(int16_t)));
2025 if (*str_offset < 0) {
2026 /* A negative indicates the cap is absent from this terminal */
2027 return "";
2028 }
2029 if (*str_offset >= str_table_len) {
2030 /* Invalid string offset */
2031 return NULL;
2032 }
2033 if (((size_t)((int)str_table_pos + (int)*str_offset)) >=
2034 global.nterminfo) {
2035 /* Truncated/corrupt terminfo? */
2036 return NULL;
2037 }
2038 return (const char *)(global.terminfo + (int)str_table_pos +
2039 (int)*str_offset);
2040 }
2041
2042 static int wait_event(struct tb_event *event, int timeout) {
2043 int rv;
2044 char buf[TB_OPT_READ_BUF];
2045 fd_set fds;
2046 struct timeval tv;
2047
2048 memset(event, 0, sizeof(*event));
2049 if_ok_return(rv, extract_event(event));
2050
2051 tv.tv_sec = timeout / 1000;
2052 tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
2053
2054 do {
2055 int maxfd, select_rv, tty_has_events, resize_has_events;
2056 FD_ZERO(&fds);
2057 FD_SET(global.rfd, &fds);
2058 FD_SET(global.resize_pipefd[0], &fds);
2059
2060 maxfd = global.resize_pipefd[0] > global.rfd
2061 ? global.resize_pipefd[0]
2062 : global.rfd;
2063
2064 select_rv = select(maxfd + 1, &fds, NULL, NULL,
2065 (timeout < 0) ? NULL : &tv);
2066
2067 if (select_rv < 0) {
2068 /* Let EINTR/EAGAIN bubble up */
2069 global.last_errno = errno;
2070 return TB_ERR_POLL;
2071 } else if (select_rv == 0) {
2072 return TB_ERR_NO_EVENT;
2073 }
2074
2075 tty_has_events = (FD_ISSET(global.rfd, &fds));
2076 resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds));
2077
2078 if (tty_has_events) {
2079 ssize_t read_rv = read(global.rfd, buf, sizeof(buf));
2080 if (read_rv < 0) {
2081 global.last_errno = errno;
2082 return TB_ERR_READ;
2083 } else if (read_rv > 0) {
2084 bytebuf_nputs(&global.in, buf, read_rv);
2085 }
2086 }
2087
2088 if (resize_has_events) {
2089 int ignore = 0;
2090 read(global.resize_pipefd[0], &ignore, sizeof(ignore));
2091 /* TODO Harden against errors encountered mid-resize */
2092 if_err_return(rv, update_term_size());
2093 if_err_return(rv, resize_cellbufs());
2094 event->type = TB_EVENT_RESIZE;
2095 event->w = global.width;
2096 event->h = global.height;
2097 return TB_OK;
2098 }
2099
2100 memset(event, 0, sizeof(*event));
2101 if_ok_return(rv, extract_event(event));
2102 } while (timeout == -1);
2103
2104 return rv;
2105 }
2106
2107 static int extract_event(struct tb_event *event) {
2108 int rv;
2109 struct bytebuf_t *in = &global.in;
2110
2111 if (in->len == 0) {
2112 return TB_ERR;
2113 }
2114
2115 if (in->buf[0] == '\x1b') {
2116 /* Escape sequence? */
2117 /* In TB_INPUT_ESC,
2118 * skip if the buffer is a single escape char */
2119 if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) {
2120 if_ok_or_need_more_return(rv, extract_esc(event));
2121 }
2122
2123 /* Escape key? */
2124 if (global.input_mode & TB_INPUT_ESC) {
2125 event->type = TB_EVENT_KEY;
2126 event->ch = 0;
2127 event->key = TB_KEY_ESC;
2128 event->mod = 0;
2129 bytebuf_shift(in, 1);
2130 return TB_OK;
2131 }
2132
2133 /* Recurse for alt key */
2134 event->mod |= TB_MOD_ALT;
2135 bytebuf_shift(in, 1);
2136 return extract_event(event);
2137 }
2138
2139 /* ASCII control key? */
2140 if ((uint16_t)in->buf[0] < TB_KEY_SPACE ||
2141 in->buf[0] == TB_KEY_BACKSPACE2)
2142 {
2143 event->type = TB_EVENT_KEY;
2144 event->ch = 0;
2145 event->key = (uint16_t)in->buf[0];
2146 event->mod |= TB_MOD_CTRL;
2147 bytebuf_shift(in, 1);
2148 return TB_OK;
2149 }
2150
2151 /* UTF-8? */
2152 if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) {
2153 event->type = TB_EVENT_KEY;
2154 tb_utf8_char_to_unicode(&event->ch, in->buf);
2155 event->key = 0;
2156 bytebuf_shift(in, tb_utf8_char_length(in->buf[0]));
2157 return TB_OK;
2158 }
2159
2160 /* Need more input */
2161 return TB_ERR;
2162 }
2163
2164 static int extract_esc(struct tb_event *event) {
2165 int rv;
2166 if_ok_or_need_more_return(rv, extract_esc_user(event, 0));
2167 if_ok_or_need_more_return(rv, extract_esc_cap(event));
2168 if_ok_or_need_more_return(rv, extract_esc_mouse(event));
2169 if_ok_or_need_more_return(rv, extract_esc_user(event, 1));
2170 return TB_ERR;
2171 }
2172
2173 static int extract_esc_user(struct tb_event *event, int is_post) {
2174 int rv;
2175 size_t consumed = 0;
2176 struct bytebuf_t *in = &global.in;
2177 int (*fn)(struct tb_event *, size_t *);
2178
2179 fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre;
2180
2181 if (!fn) {
2182 return TB_ERR;
2183 }
2184
2185 rv = fn(event, &consumed);
2186 if (rv == TB_OK) {
2187 bytebuf_shift(in, consumed);
2188 }
2189
2190 if_ok_or_need_more_return(rv, rv);
2191 return TB_ERR;
2192 }
2193
2194 static int extract_esc_cap(struct tb_event *event) {
2195 int rv;
2196 struct bytebuf_t *in = &global.in;
2197 struct cap_trie_t *node;
2198 size_t depth;
2199
2200 if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth));
2201 if (node->is_leaf) {
2202 /* Found a leaf node */
2203 event->type = TB_EVENT_KEY;
2204 event->ch = 0;
2205 event->key = node->key;
2206 event->mod = node->mod;
2207 bytebuf_shift(in, depth);
2208 return TB_OK;
2209 } else if (node->nchildren > 0 && in->len <= depth) {
2210 /* Found a branch node (not enough input) */
2211 return TB_ERR_NEED_MORE;
2212 }
2213
2214 return TB_ERR;
2215 }
2216
2217 static int extract_esc_mouse(struct tb_event *event) {
2218 struct bytebuf_t *in = &global.in;
2219
2220 enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX };
2221
2222 char *cmp[TYPE_MAX];
2223
2224 enum type type = 0;
2225 int ret = TB_ERR;
2226 size_t buf_shift = 0;
2227
2228 /* X10 mouse encoding, the simplest one */
2229 /* \x1b [ M Cb Cx Cy */
2230 cmp[TYPE_VT200] = "\x1b[M";
2231 /* xterm 1006 extended mode or urxvt 1015 extended mode */
2232 /* xterm: \x1b [ < Cb ; Cx ; Cy (M or m) */
2233 cmp[TYPE_1006] = "\x1b[<";
2234 /* urxvt: \x1b [ Cb ; Cx ; Cy M */
2235 cmp[TYPE_1015] = "\x1b[";
2236
2237 /* Unrolled at compile-time (probably) */
2238 for (; type < TYPE_MAX; type++) {
2239 size_t size = strlen(cmp[type]);
2240
2241 if (in->len >= size &&
2242 (strncmp(cmp[type], in->buf, size)) == 0) {
2243 break;
2244 }
2245 }
2246
2247 if (type == TYPE_MAX) {
2248 ret = TB_ERR; /* No match */
2249 return ret;
2250 }
2251
2252 switch (type) {
2253 case TYPE_VT200:
2254 {
2255 int b, fail;
2256 if (in->len < 6) break;
2257
2258 b = in->buf[3] - 0x20;
2259 fail = 0;
2260
2261 switch (b & 3) {
2262 case 0:
2263 event->key = ((b & 64) != 0) ?
2264 TB_KEY_MOUSE_WHEEL_UP :
2265 TB_KEY_MOUSE_LEFT;
2266 break;
2267 case 1:
2268 event->key = ((b & 64) != 0) ?
2269 TB_KEY_MOUSE_WHEEL_DOWN :
2270 TB_KEY_MOUSE_MIDDLE;
2271 break;
2272 case 2:
2273 event->key = TB_KEY_MOUSE_RIGHT;
2274 break;
2275 case 3:
2276 event->key = TB_KEY_MOUSE_RELEASE;
2277 break;
2278 default:
2279 ret = TB_ERR;
2280 fail = 1;
2281 break;
2282 }
2283
2284 if (!fail) {
2285 if ((b & 32) != 0) {
2286 event->mod |= TB_MOD_MOTION;
2287 }
2288
2289 /* the coord is 1,1 for upper left */
2290 event->x = ((uint8_t)in->buf[4]) - 0x21;
2291 event->y = ((uint8_t)in->buf[5]) - 0x21;
2292
2293 ret = TB_OK;
2294 }
2295
2296 buf_shift = 6;
2297 }
2298 break;
2299 case TYPE_1006:
2300 /* fallthrough */
2301 case TYPE_1015:
2302 {
2303 size_t i = 0;
2304
2305 enum {
2306 FIRST_M = 0,
2307 FIRST_SEMICOLON,
2308 LAST_SEMICOLON,
2309 FIRST_LAST_MAX
2310 };
2311
2312 size_t indices[FIRST_LAST_MAX] = {
2313 index_fail,
2314 index_fail,
2315 index_fail
2316 };
2317 int m_is_capital = 0;
2318
2319 for (i = 0; i < in->len; i++) {
2320 if (in->buf[i] != ';') {
2321 if (indices[FIRST_SEMICOLON] == index_fail) {
2322 indices[FIRST_SEMICOLON] = i;
2323 } else {
2324 indices[LAST_SEMICOLON] = i;
2325 }
2326 } else if (indices[FIRST_M] == index_fail) {
2327 if (in->buf[i] == 'm' || in->buf[i] == 'M') {
2328 m_is_capital = (in->buf[i] == 'M');
2329 indices[FIRST_M] = i;
2330 }
2331 }
2332 }
2333
2334 if (indices[FIRST_M] == index_fail ||
2335 indices[FIRST_SEMICOLON] == index_fail ||
2336 indices[LAST_SEMICOLON] == index_fail) {
2337 ret = TB_ERR;
2338 } else {
2339 int start = (type == TYPE_1015 ? 2 : 3);
2340 int fail = 0;
2341
2342 unsigned n1 = strtoul(&in->buf[start], NULL, 10);
2343 unsigned n2 = strtoul(
2344 &in->buf[indices[FIRST_SEMICOLON] + 1],
2345 NULL, 10);
2346 unsigned n3 = strtoul(
2347 &in->buf[indices[LAST_SEMICOLON] + 1],
2348 NULL, 10);
2349
2350 if (type == TYPE_1015) {
2351 n1 -= 0x20;
2352 }
2353
2354
2355 switch (n1 & 3) {
2356 case 0:
2357 event->key = ((n1 & 64) != 0)
2358 ? TB_KEY_MOUSE_WHEEL_UP
2359 : TB_KEY_MOUSE_LEFT;
2360 break;
2361 case 1:
2362 event->key = ((n1 & 64) != 0)
2363 ? TB_KEY_MOUSE_WHEEL_DOWN
2364 : TB_KEY_MOUSE_MIDDLE;
2365 break;
2366 case 2:
2367 event->key = TB_KEY_MOUSE_RIGHT;
2368 break;
2369 case 3:
2370 event->key = TB_KEY_MOUSE_RELEASE;
2371 break;
2372 default:
2373 ret = TB_ERR;
2374 fail = 1;
2375 break;
2376 }
2377
2378 buf_shift = in->len;
2379
2380 if (!fail) {
2381 if (!m_is_capital) {
2382 /* on xterm mouse release is signaled
2383 * by lowercase m */
2384 event->key = TB_KEY_MOUSE_RELEASE;
2385 }
2386
2387 if ((n1 & 32) != 0) {
2388 event->mod |= TB_MOD_MOTION;
2389 }
2390
2391 event->x = ((uint8_t)n2) - 1;
2392 event->y = ((uint8_t)n3) - 1;
2393
2394 ret = TB_OK;
2395 }
2396 }
2397 }
2398 break;
2399 case TYPE_MAX:
2400 ret = TB_ERR;
2401 }
2402
2403 if (buf_shift > 0) {
2404 bytebuf_shift(in, buf_shift);
2405 }
2406
2407 if (ret == TB_OK) {
2408 event->type = TB_EVENT_MOUSE;
2409 }
2410
2411 return ret;
2412 }
2413
2414 static int resize_cellbufs(void) {
2415 int rv;
2416 if_err_return(rv, cellbuf_resize(&global.back, global.width,
2417 global.height));
2418 if_err_return(rv, cellbuf_resize(&global.front, global.width,
2419 global.height));
2420 if_err_return(rv, cellbuf_clear(&global.front));
2421 if_err_return(rv, send_clear());
2422 return TB_OK;
2423 }
2424
2425 static void handle_resize(int sig) {
2426 int errno_copy = errno;
2427 write(global.resize_pipefd[1], &sig, sizeof(sig));
2428 errno = errno_copy;
2429 }
2430
2431 static int send_attr(uintattr_t fg, uintattr_t bg) {
2432 int rv;
2433 uintattr_t attr_bold, attr_blink, attr_italic,
2434 attr_underline, attr_reverse, attr_default;
2435 uintattr_t cfg, cbg;
2436
2437 if (fg == global.last_fg && bg == global.last_bg) {
2438 return TB_OK;
2439 }
2440
2441 if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]));
2442
2443 switch (global.output_mode) {
2444 default:
2445 case TB_OUTPUT_NORMAL:
2446 cfg = fg & 0x0f;
2447 cbg = bg & 0x0f;
2448 break;
2449
2450 case TB_OUTPUT_256:
2451 cfg = fg & 0xff;
2452 cbg = bg & 0xff;
2453 break;
2454
2455 case TB_OUTPUT_216:
2456 cfg = fg & 0xff;
2457 cbg = bg & 0xff;
2458 if (cfg > 216)
2459 cfg = 216;
2460 if (cbg > 216)
2461 cbg = 216;
2462 cfg += 0x0f;
2463 cbg += 0x0f;
2464 break;
2465
2466 case TB_OUTPUT_GRAYSCALE:
2467 cfg = fg & 0xff;
2468 cbg = bg & 0xff;
2469 if (cfg > 24)
2470 cfg = 24;
2471 if (cbg > 24)
2472 cbg = 24;
2473 cfg += 0xe7;
2474 cbg += 0xe7;
2475 break;
2476
2477 #ifdef TB_OPT_TRUECOLOR
2478 case TB_OUTPUT_TRUECOLOR:
2479 cfg = fg & 0xffffff;
2480 cbg = bg & 0xffffff;
2481 break;
2482 #endif
2483 }
2484
2485 #ifdef TB_OPT_TRUECOLOR
2486 if (global.output_mode == TB_OUTPUT_TRUECOLOR) {
2487 attr_bold = TB_TRUECOLOR_BOLD;
2488 attr_blink = TB_TRUECOLOR_BLINK;
2489 attr_italic = TB_TRUECOLOR_ITALIC;
2490 attr_underline = TB_TRUECOLOR_UNDERLINE;
2491 attr_reverse = TB_TRUECOLOR_REVERSE;
2492 attr_default = TB_TRUECOLOR_DEFAULT;
2493 } else
2494 #endif
2495 {
2496 attr_bold = TB_BOLD;
2497 attr_blink = TB_BLINK;
2498 attr_italic = TB_ITALIC;
2499 attr_underline = TB_UNDERLINE;
2500 attr_reverse = TB_REVERSE;
2501 attr_default = TB_DEFAULT;
2502 }
2503
2504 /* For convenience (and some back compat), interpret 0 as default in
2505 * some modes */
2506 if (global.output_mode == TB_OUTPUT_NORMAL ||
2507 global.output_mode == TB_OUTPUT_216 ||
2508 global.output_mode == TB_OUTPUT_GRAYSCALE)
2509 {
2510 if ((fg & 0xff) == 0)
2511 fg |= attr_default;
2512 if ((bg & 0xff) == 0)
2513 bg |= attr_default;
2514 }
2515
2516 if (fg & attr_bold)
2517 if_err_return(rv, bytebuf_puts(&global.out,
2518 global.caps[TB_CAP_BOLD]));
2519
2520 if (fg & attr_blink)
2521 if_err_return(rv, bytebuf_puts(&global.out,
2522 global.caps[TB_CAP_BLINK]));
2523
2524 if (fg & attr_underline)
2525 if_err_return(rv, bytebuf_puts(&global.out,
2526 global.caps[TB_CAP_UNDERLINE]));
2527
2528 if (fg & attr_italic)
2529 if_err_return(rv, bytebuf_puts(&global.out,
2530 global.caps[TB_CAP_ITALIC]));
2531
2532 if ((fg & attr_reverse) || (bg & attr_reverse))
2533 if_err_return(rv, bytebuf_puts(&global.out,
2534 global.caps[TB_CAP_REVERSE]));
2535
2536 if_err_return(rv, send_sgr(cfg, cbg, fg & attr_default,
2537 bg & attr_default));
2538
2539 global.last_fg = fg;
2540 global.last_bg = bg;
2541
2542 return TB_OK;
2543 }
2544
2545 static int send_sgr(uintattr_t cfg, uintattr_t cbg, uintattr_t fg_is_default,
2546 uintattr_t bg_is_default) {
2547 int rv;
2548 char nbuf[32];
2549
2550 if (fg_is_default && bg_is_default) {
2551 return TB_OK;
2552 }
2553
2554 switch (global.output_mode) {
2555 default:
2556 case TB_OUTPUT_NORMAL:
2557 send_literal(rv, "\x1b[");
2558 if (!fg_is_default) {
2559 send_literal(rv, "3");
2560 send_num(rv, nbuf, cfg - 1);
2561 if (!bg_is_default) {
2562 send_literal(rv, ";");
2563 }
2564 }
2565 if (!bg_is_default) {
2566 send_literal(rv, "4");
2567 send_num(rv, nbuf, cbg - 1);
2568 }
2569 send_literal(rv, "m");
2570 break;
2571
2572 case TB_OUTPUT_256:
2573 case TB_OUTPUT_216:
2574 case TB_OUTPUT_GRAYSCALE:
2575 send_literal(rv, "\x1b[");
2576 if (!fg_is_default) {
2577 send_literal(rv, "38;5;");
2578 send_num(rv, nbuf, cfg);
2579 if (!bg_is_default) {
2580 send_literal(rv, ";");
2581 }
2582 }
2583 if (!bg_is_default) {
2584 send_literal(rv, "48;5;");
2585 send_num(rv, nbuf, cbg);
2586 }
2587 send_literal(rv, "m");
2588 break;
2589
2590 #ifdef TB_OPT_TRUECOLOR
2591 case TB_OUTPUT_TRUECOLOR:
2592 send_literal(rv, "\x1b[");
2593 if (!fg_is_default) {
2594 send_literal(rv, "38;2;");
2595 send_num(rv, nbuf, (cfg >> 16) & 0xff);
2596 send_literal(rv, ";");
2597 send_num(rv, nbuf, (cfg >> 8) & 0xff);
2598 send_literal(rv, ";");
2599 send_num(rv, nbuf, cfg & 0xff);
2600 if (!bg_is_default) {
2601 send_literal(rv, ";");
2602 }
2603 }
2604 if (!bg_is_default) {
2605 send_literal(rv, "48;2;");
2606 send_num(rv, nbuf, (cbg >> 16) & 0xff);
2607 send_literal(rv, ";");
2608 send_num(rv, nbuf, (cbg >> 8) & 0xff);
2609 send_literal(rv, ";");
2610 send_num(rv, nbuf, cbg & 0xff);
2611 }
2612 send_literal(rv, "m");
2613 break;
2614 #endif
2615 }
2616 return TB_OK;
2617 }
2618
2619 static int send_cursor_if(int x, int y) {
2620 int rv;
2621 char nbuf[32];
2622 if (x < 0 || y < 0) {
2623 return TB_OK;
2624 }
2625 send_literal(rv, "\x1b[");
2626 send_num(rv, nbuf, y + 1);
2627 send_literal(rv, ";");
2628 send_num(rv, nbuf, x + 1);
2629 send_literal(rv, "H");
2630 return TB_OK;
2631 }
2632
2633 static int send_char(int x, int y, uint32_t ch) {
2634 return send_cluster(x, y, &ch, 1);
2635 }
2636
2637 static int send_cluster(int x, int y, uint32_t *ch, size_t nch) {
2638 int rv, i;
2639 char abuf[8];
2640
2641 if (global.last_x != x - 1 || global.last_y != y) {
2642 if_err_return(rv, send_cursor_if(x, y));
2643 }
2644 global.last_x = x;
2645 global.last_y = y;
2646
2647 for (i = 0; i < (int)nch; i++) {
2648 uint32_t ach = *(ch + i);
2649 int aw = tb_utf8_unicode_to_char(abuf, ach);
2650 if (!ach) {
2651 abuf[0] = ' ';
2652 }
2653 if_err_return(rv,
2654 bytebuf_nputs(&global.out, abuf, (size_t)aw));
2655 }
2656
2657 return TB_OK;
2658 }
2659
2660 static int convert_num(uint32_t num, char *buf) {
2661 int i, l = 0;
2662 char ch;
2663 do {
2664 /* '0' = 48; 48 + num%10 < 58 < MAX_8bitCHAR */
2665 buf[l++] = (char)('0' + (num % 10));
2666 num /= 10;
2667 } while (num);
2668 for (i = 0; i < l / 2; i++) {
2669 ch = buf[i];
2670 buf[i] = buf[l - 1 - i];
2671 buf[l - 1 - i] = ch;
2672 }
2673 return l;
2674 }
2675
2676 static int cell_cmp(struct tb_cell *a, struct tb_cell *b) {
2677 if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) {
2678 return 1;
2679 }
2680 #ifdef TB_OPT_EGC
2681 if (a->nech != b->nech) {
2682 return 1;
2683 } else if (a->nech > 0) { /* a->nech == b->nech */
2684 return memcmp(a->ech, b->ech, a->nech);
2685 }
2686 #endif
2687 return 0;
2688 }
2689
2690 static int cell_copy(struct tb_cell *dst, struct tb_cell *src) {
2691 #ifdef TB_OPT_EGC
2692 if (src->nech > 0) {
2693 return cell_set(dst, src->ech, src->nech, src->fg, src->bg);
2694 }
2695 #endif
2696 return cell_set(dst, &src->ch, 1, src->fg, src->bg);
2697 }
2698
2699 static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
2700 uintattr_t fg, uintattr_t bg) {
2701 cell->ch = ch ? *ch : 0;
2702 cell->fg = fg;
2703 cell->bg = bg;
2704 #ifdef TB_OPT_EGC
2705 if (nch <= 1) {
2706 cell->nech = 0;
2707 } else {
2708 int rv;
2709 if_err_return(rv, cell_reserve_ech(cell, nch + 1));
2710 memcpy(cell->ech, ch, nch);
2711 cell->ech[nch] = '\0';
2712 cell->nech = nch;
2713 }
2714 #else
2715 (void)nch;
2716 (void)cell_reserve_ech;
2717 #endif
2718 return TB_OK;
2719 }
2720
2721 static int cell_reserve_ech(struct tb_cell *cell, size_t n) {
2722 #ifdef TB_OPT_EGC
2723 if (cell->cech >= n) {
2724 return TB_OK;
2725 }
2726 if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) {
2727 return TB_ERR_MEM;
2728 }
2729 cell->cech = n;
2730 return TB_OK;
2731 #else
2732 (void)cell;
2733 (void)n;
2734 return TB_ERR;
2735 #endif
2736 }
2737
2738 static int cell_free(struct tb_cell *cell) {
2739 #ifdef TB_OPT_EGC
2740 if (cell->ech) {
2741 tb_free(cell->ech);
2742 }
2743 #endif
2744 memset(cell, 0, sizeof(*cell));
2745 return TB_OK;
2746 }
2747
2748 static int cellbuf_init(struct cellbuf_t *c, int w, int h) {
2749 c->cells = tb_malloc(sizeof(struct tb_cell) * w * h);
2750 if (!c->cells) {
2751 return TB_ERR_MEM;
2752 }
2753 memset(c->cells, 0, sizeof(struct tb_cell) * w * h);
2754 c->width = w;
2755 c->height = h;
2756 return TB_OK;
2757 }
2758
2759 static int cellbuf_free(struct cellbuf_t *c) {
2760 if (c->cells) {
2761 int i;
2762 for (i = 0; i < c->width * c->height; i++) {
2763 cell_free(&c->cells[i]);
2764 }
2765 tb_free(c->cells);
2766 }
2767 memset(c, 0, sizeof(*c));
2768 return TB_OK;
2769 }
2770
2771 static int cellbuf_clear(struct cellbuf_t *c) {
2772 int rv, i;
2773 uint32_t space = (uint32_t)' ';
2774 for (i = 0; i < c->width * c->height; i++) {
2775 if_err_return(rv, cell_set(
2776 &c->cells[i], &space, 1, global.fg, global.bg));
2777
2778 }
2779 return TB_OK;
2780 }
2781
2782 static int cellbuf_get(struct cellbuf_t *c, int x, int y,
2783 struct tb_cell **out) {
2784 if (x < 0 || x >= c->width || y < 0 || y >= c->height) {
2785 *out = NULL;
2786 return TB_ERR_OUT_OF_BOUNDS;
2787 }
2788 *out = &c->cells[(y * c->width) + x];
2789 return TB_OK;
2790 }
2791
2792 static int cellbuf_resize(struct cellbuf_t *c, int w, int h) {
2793 int rv;
2794
2795 int ow = c->width;
2796 int oh = c->height;
2797 int minw, minh, x, y;
2798 struct tb_cell *prev;
2799
2800 if (ow == w && oh == h) {
2801 return TB_OK;
2802 }
2803
2804 w = w < 1 ? 1 : w;
2805 h = h < 1 ? 1 : h;
2806
2807 minw = (w < ow) ? w : ow;
2808 minh = (h < oh) ? h : oh;
2809
2810 prev = c->cells;
2811
2812 if_err_return(rv, cellbuf_init(c, w, h));
2813 if_err_return(rv, cellbuf_clear(c));
2814
2815 x = 0;
2816 while (x < minw) {
2817 y = 0;
2818 while (y < minh) {
2819 struct tb_cell *src, *dst;
2820 src = &prev[(y * ow) + x];
2821 if_err_return(rv, cellbuf_get(c, x, y, &dst));
2822 if_err_return(rv, cell_copy(dst, src));
2823 y++;
2824 }
2825 x++;
2826 }
2827
2828 tb_free(prev);
2829
2830 return TB_OK;
2831 }
2832
2833 static int bytebuf_puts(struct bytebuf_t *b, const char *str) {
2834 return bytebuf_nputs(b, str, (size_t)strlen(str));
2835 }
2836
2837 static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) {
2838 int rv;
2839 if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1));
2840 memcpy(b->buf + b->len, str, nstr);
2841 b->len += nstr;
2842 b->buf[b->len] = '\0';
2843 return TB_OK;
2844 }
2845
2846 static int bytebuf_shift(struct bytebuf_t *b, size_t n) {
2847 size_t nmove;
2848 if (n > b->len) {
2849 n = b->len;
2850 }
2851 nmove = b->len - n;
2852 memmove(b->buf, b->buf + n, nmove);
2853 b->len -= n;
2854 return TB_OK;
2855 }
2856
2857 static int bytebuf_flush(struct bytebuf_t *b, int fd) {
2858 ssize_t write_rv;
2859 if (b->len <= 0) {
2860 return TB_OK;
2861 }
2862 write_rv = write(fd, b->buf, b->len);
2863 if (write_rv < 0 || (size_t)write_rv != b->len) {
2864 /* Note, errno will be 0 on partial write */
2865 global.last_errno = errno;
2866 return TB_ERR;
2867 }
2868 b->len = 0;
2869 return TB_OK;
2870 }
2871
2872 static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) {
2873 char *newbuf;
2874 size_t newcap;
2875
2876 if (b->cap >= sz) {
2877 return TB_OK;
2878 }
2879 newcap = b->cap > 0 ? b->cap : 1;
2880 while (newcap < sz) {
2881 newcap *= 2;
2882 }
2883 if (b->buf) {
2884 newbuf = tb_realloc(b->buf, newcap);
2885 } else {
2886 newbuf = tb_malloc(newcap);
2887 }
2888 if (!newbuf) {
2889 return TB_ERR_MEM;
2890 }
2891 b->buf = newbuf;
2892 b->cap = newcap;
2893 return TB_OK;
2894 }
2895
2896 static int bytebuf_free(struct bytebuf_t *b) {
2897 if (b->buf) {
2898 tb_free(b->buf);
2899 }
2900 memset(b, 0, sizeof(*b));
2901 return TB_OK;
2902 }
2903