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