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