Fast and reasonably complete (framebuffer) terminal emulator (Zig fork)
1/* Copyright (C) 2022-2025 mintsuki and contributors.
2 *
3 * Redistribution and use in source and binary forms, with or without
4 * modification, are permitted provided that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain the above copyright notice,
7 * this list of conditions and the following disclaimer.
8 *
9 * 2. Redistributions in binary form must reproduce the above copyright notice,
10 * this list of conditions and the following disclaimer in the documentation
11 * and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23 * POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#ifdef __cplusplus
27#error "Please do not compile Flanterm as C++ code! Flanterm should be compiled as C99 or newer."
28#endif
29
30#ifndef __STDC_VERSION__
31#error "Flanterm must be compiled as C99 or newer."
32#endif
33
34#include <stdint.h>
35#include <stddef.h>
36#include <stdbool.h>
37
38#ifndef FLANTERM_IN_FLANTERM
39#define FLANTERM_IN_FLANTERM
40#endif
41
42#include "flanterm.h"
43
44// Tries to implement this standard for terminfo
45// https://man7.org/linux/man-pages/man4/console_codes.4.html
46
47static const uint32_t col256[] = {
48 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
49 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
50 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
51 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
52 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
53 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
54 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
55 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
56 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
57 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
58 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
59 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
60 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
61 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
62 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
63 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
64 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
65 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
66 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
67 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
68 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
69 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
70 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
71 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
72 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
73 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
74 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
75 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
76 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
77 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
78};
79
80#define CHARSET_DEFAULT 0
81#define CHARSET_DEC_SPECIAL 1
82
83void flanterm_context_reinit(struct flanterm_context *ctx) {
84 ctx->tab_size = 8;
85 ctx->autoflush = true;
86 ctx->cursor_enabled = true;
87 ctx->scroll_enabled = true;
88 ctx->control_sequence = false;
89 ctx->escape = false;
90 ctx->osc = false;
91 ctx->osc_escape = false;
92 ctx->rrr = false;
93 ctx->discard_next = false;
94 ctx->bold = false;
95 ctx->bg_bold = false;
96 ctx->reverse_video = false;
97 ctx->dec_private = false;
98 ctx->insert_mode = false;
99 ctx->csi_unhandled = false;
100 ctx->unicode_remaining = 0;
101 ctx->g_select = 0;
102 ctx->charsets[0] = CHARSET_DEFAULT;
103 ctx->charsets[1] = CHARSET_DEC_SPECIAL;
104 ctx->current_charset = 0;
105 ctx->escape_offset = 0;
106 ctx->esc_values_i = 0;
107 ctx->saved_cursor_x = 0;
108 ctx->saved_cursor_y = 0;
109 ctx->current_primary = (size_t)-1;
110 ctx->current_bg = (size_t)-1;
111 ctx->scroll_top_margin = 0;
112 ctx->scroll_bottom_margin = ctx->rows;
113 ctx->oob_output = FLANTERM_OOB_OUTPUT_ONLCR;
114}
115
116static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c);
117
118void flanterm_write(struct flanterm_context *ctx, const char *buf, size_t count) {
119 for (size_t i = 0; i < count; i++) {
120 flanterm_putchar(ctx, buf[i]);
121 }
122
123 if (ctx->autoflush) {
124 ctx->double_buffer_flush(ctx);
125 }
126}
127
128static void sgr(struct flanterm_context *ctx) {
129 size_t i = 0;
130
131 if (!ctx->esc_values_i)
132 goto def;
133
134 for (; i < ctx->esc_values_i; i++) {
135 size_t offset;
136
137 if (ctx->esc_values[i] == 0) {
138def:
139 if (ctx->reverse_video) {
140 ctx->reverse_video = false;
141 ctx->swap_palette(ctx);
142 }
143 ctx->bold = false;
144 ctx->bg_bold = false;
145 ctx->current_primary = (size_t)-1;
146 ctx->current_bg = (size_t)-1;
147 ctx->set_text_bg_default(ctx);
148 ctx->set_text_fg_default(ctx);
149 continue;
150 }
151
152 else if (ctx->esc_values[i] == 1) {
153 ctx->bold = true;
154 if (ctx->current_primary != (size_t)-1) {
155 if (!ctx->reverse_video) {
156 ctx->set_text_fg_bright(ctx, ctx->current_primary);
157 } else {
158 ctx->set_text_bg_bright(ctx, ctx->current_primary);
159 }
160 } else {
161 if (!ctx->reverse_video) {
162 ctx->set_text_fg_default_bright(ctx);
163 } else {
164 ctx->set_text_bg_default_bright(ctx);
165 }
166 }
167 continue;
168 }
169
170 else if (ctx->esc_values[i] == 5) {
171 ctx->bg_bold = true;
172 if (ctx->current_bg != (size_t)-1) {
173 if (!ctx->reverse_video) {
174 ctx->set_text_bg_bright(ctx, ctx->current_bg);
175 } else {
176 ctx->set_text_fg_bright(ctx, ctx->current_bg);
177 }
178 } else {
179 if (!ctx->reverse_video) {
180 ctx->set_text_bg_default_bright(ctx);
181 } else {
182 ctx->set_text_fg_default_bright(ctx);
183 }
184 }
185 continue;
186 }
187
188 else if (ctx->esc_values[i] == 22) {
189 ctx->bold = false;
190 if (ctx->current_primary != (size_t)-1) {
191 if (!ctx->reverse_video) {
192 ctx->set_text_fg(ctx, ctx->current_primary);
193 } else {
194 ctx->set_text_bg(ctx, ctx->current_primary);
195 }
196 } else {
197 if (!ctx->reverse_video) {
198 ctx->set_text_fg_default(ctx);
199 } else {
200 ctx->set_text_bg_default(ctx);
201 }
202 }
203 continue;
204 }
205
206 else if (ctx->esc_values[i] == 25) {
207 ctx->bg_bold = false;
208 if (ctx->current_bg != (size_t)-1) {
209 if (!ctx->reverse_video) {
210 ctx->set_text_bg(ctx, ctx->current_bg);
211 } else {
212 ctx->set_text_fg(ctx, ctx->current_bg);
213 }
214 } else {
215 if (!ctx->reverse_video) {
216 ctx->set_text_bg_default(ctx);
217 } else {
218 ctx->set_text_fg_default(ctx);
219 }
220 }
221 continue;
222 }
223
224 else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
225 offset = 30;
226 ctx->current_primary = ctx->esc_values[i] - offset;
227
228 if (ctx->reverse_video) {
229 goto set_bg;
230 }
231
232set_fg:
233 if ((ctx->bold && !ctx->reverse_video)
234 || (ctx->bg_bold && ctx->reverse_video)) {
235 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
236 } else {
237 ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
238 }
239 continue;
240 }
241
242 else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
243 offset = 40;
244 ctx->current_bg = ctx->esc_values[i] - offset;
245
246 if (ctx->reverse_video) {
247 goto set_fg;
248 }
249
250set_bg:
251 if ((ctx->bold && ctx->reverse_video)
252 || (ctx->bg_bold && !ctx->reverse_video)) {
253 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
254 } else {
255 ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
256 }
257 continue;
258 }
259
260 else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
261 offset = 90;
262 ctx->current_primary = ctx->esc_values[i] - offset;
263
264 if (ctx->reverse_video) {
265 goto set_bg_bright;
266 }
267
268set_fg_bright:
269 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
270 continue;
271 }
272
273 else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
274 offset = 100;
275 ctx->current_bg = ctx->esc_values[i] - offset;
276
277 if (ctx->reverse_video) {
278 goto set_fg_bright;
279 }
280
281set_bg_bright:
282 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
283 continue;
284 }
285
286 else if (ctx->esc_values[i] == 39) {
287 ctx->current_primary = (size_t)-1;
288
289 if (ctx->reverse_video) {
290 ctx->swap_palette(ctx);
291 }
292
293 if (!ctx->bold) {
294 ctx->set_text_fg_default(ctx);
295 } else {
296 ctx->set_text_fg_default_bright(ctx);
297 }
298
299 if (ctx->reverse_video) {
300 ctx->swap_palette(ctx);
301 }
302
303 continue;
304 }
305
306 else if (ctx->esc_values[i] == 49) {
307 ctx->current_bg = (size_t)-1;
308
309 if (ctx->reverse_video) {
310 ctx->swap_palette(ctx);
311 }
312
313 if (!ctx->bg_bold) {
314 ctx->set_text_bg_default(ctx);
315 } else {
316 ctx->set_text_bg_default_bright(ctx);
317 }
318
319 if (ctx->reverse_video) {
320 ctx->swap_palette(ctx);
321 }
322
323 continue;
324 }
325
326 else if (ctx->esc_values[i] == 7) {
327 if (!ctx->reverse_video) {
328 ctx->reverse_video = true;
329 ctx->swap_palette(ctx);
330 }
331 continue;
332 }
333
334 else if (ctx->esc_values[i] == 27) {
335 if (ctx->reverse_video) {
336 ctx->reverse_video = false;
337 ctx->swap_palette(ctx);
338 }
339 continue;
340 }
341
342 // 256/RGB
343 else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
344 bool fg = ctx->esc_values[i] == 38;
345
346 i++;
347 if (i >= ctx->esc_values_i) {
348 break;
349 }
350
351 switch (ctx->esc_values[i]) {
352 case 2: { // RGB
353 if (i + 3 >= ctx->esc_values_i) {
354 goto out;
355 }
356
357 uint32_t rgb_value = 0;
358
359 rgb_value |= ctx->esc_values[i + 1] << 16;
360 rgb_value |= ctx->esc_values[i + 2] << 8;
361 rgb_value |= ctx->esc_values[i + 3];
362
363 i += 3;
364
365 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
366
367 break;
368 }
369 case 5: { // 256 colors
370 if (i + 1 >= ctx->esc_values_i) {
371 goto out;
372 }
373
374 uint32_t col = ctx->esc_values[i + 1];
375
376 i++;
377
378 if (col < 8) {
379 (fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
380 } else if (col < 16) {
381 (fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx, col - 8);
382 } else if (col < 256) {
383 uint32_t rgb_value = col256[col - 16];
384 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
385 }
386
387 break;
388 }
389 default: continue;
390 }
391 }
392 }
393
394out:;
395}
396
397static void dec_private_parse(struct flanterm_context *ctx, uint8_t c) {
398 ctx->dec_private = false;
399
400 if (ctx->esc_values_i == 0) {
401 return;
402 }
403
404 bool set;
405
406 switch (c) {
407 case 'h':
408 set = true; break;
409 case 'l':
410 set = false; break;
411 default:
412 return;
413 }
414
415 switch (ctx->esc_values[0]) {
416 case 25: {
417 if (set) {
418 ctx->cursor_enabled = true;
419 } else {
420 ctx->cursor_enabled = false;
421 }
422 return;
423 }
424 }
425
426 if (ctx->callback != NULL) {
427 ctx->callback(ctx, FLANTERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
428 }
429}
430
431static void linux_private_parse(struct flanterm_context *ctx) {
432 if (ctx->esc_values_i == 0) {
433 return;
434 }
435
436 if (ctx->callback != NULL) {
437 ctx->callback(ctx, FLANTERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
438 }
439}
440
441static void mode_toggle(struct flanterm_context *ctx, uint8_t c) {
442 if (ctx->esc_values_i == 0) {
443 return;
444 }
445
446 bool set;
447
448 switch (c) {
449 case 'h':
450 set = true; break;
451 case 'l':
452 set = false; break;
453 default:
454 return;
455 }
456
457 switch (ctx->esc_values[0]) {
458 case 4:
459 ctx->insert_mode = set; return;
460 }
461
462 if (ctx->callback != NULL) {
463 ctx->callback(ctx, FLANTERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
464 }
465}
466
467static bool osc_parse(struct flanterm_context *ctx, uint8_t c) {
468 // ESC \ terminates an OSC sequence cleanly
469 // but if ESC is followed by non-\, report failure from osc_parse and
470 // try parsing the character as another escape code
471 if (ctx->osc_escape) {
472 if (c == '\\') {
473 ctx->osc = false;
474 ctx->osc_escape = false;
475 ctx->escape = false;
476 return true;
477 } else {
478 ctx->osc_escape = false;
479 ctx->osc = false;
480 // escape stays true here
481 return false;
482 }
483 }
484 switch (c) {
485 case 0x1b:
486 ctx->osc_escape = true;
487 break;
488 // BEL is the other terminator
489 case '\a':
490 ctx->osc_escape = false;
491 ctx->osc = false;
492 ctx->escape = false;
493 break;
494 default:
495 break;
496 }
497 return true;
498}
499
500static void control_sequence_parse(struct flanterm_context *ctx, uint8_t c) {
501 if (ctx->escape_offset == 2) {
502 switch (c) {
503 case '[':
504 ctx->discard_next = true;
505 goto cleanup;
506 case '?':
507 ctx->dec_private = true;
508 return;
509 }
510 }
511
512 if (c >= '0' && c <= '9') {
513 if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
514 return;
515 }
516 ctx->rrr = true;
517 ctx->esc_values[ctx->esc_values_i] *= 10;
518 ctx->esc_values[ctx->esc_values_i] += c - '0';
519 return;
520 }
521
522 if (ctx->rrr == true) {
523 ctx->esc_values_i++;
524 ctx->rrr = false;
525 if (c == ';')
526 return;
527 } else if (c == ';') {
528 if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
529 return;
530 }
531 ctx->esc_values[ctx->esc_values_i] = 0;
532 ctx->esc_values_i++;
533 return;
534 }
535
536 size_t esc_default;
537 switch (c) {
538 case 'J': case 'K': case 'q':
539 esc_default = 0; break;
540 default:
541 esc_default = 1; break;
542 }
543
544 for (size_t i = ctx->esc_values_i; i < FLANTERM_MAX_ESC_VALUES; i++) {
545 ctx->esc_values[i] = esc_default;
546 }
547
548 if (ctx->dec_private == true) {
549 dec_private_parse(ctx, c);
550 goto cleanup;
551 }
552
553 bool r = ctx->scroll_enabled;
554 ctx->scroll_enabled = false;
555 size_t x, y;
556 ctx->get_cursor_pos(ctx, &x, &y);
557
558 // CSI sequences are terminated by a byte in [0x40,0x7E]
559 // so skip all bytes until the terminator byte
560 if (ctx->csi_unhandled) {
561 if (c >= 0x40 && c <= 0x7E) {
562 ctx->csi_unhandled = false;
563 goto cleanup;
564 }
565 return;
566 }
567
568 switch (c) {
569 // Got ESC in the middle of an escape sequence, start a new one
570 case 0x1B:
571 return;
572 case 'F':
573 x = 0;
574 // FALLTHRU
575 case 'A': {
576 if (ctx->esc_values[0] > y)
577 ctx->esc_values[0] = y;
578 size_t orig_y = y;
579 size_t dest_y = y - ctx->esc_values[0];
580 bool will_be_in_scroll_region = false;
581 if ((ctx->scroll_top_margin >= dest_y && ctx->scroll_top_margin <= orig_y)
582 || (ctx->scroll_bottom_margin >= dest_y && ctx->scroll_bottom_margin <= orig_y)) {
583 will_be_in_scroll_region = true;
584 }
585 if (will_be_in_scroll_region && dest_y < ctx->scroll_top_margin) {
586 dest_y = ctx->scroll_top_margin;
587 }
588 ctx->set_cursor_pos(ctx, x, dest_y);
589 break;
590 }
591 case 'E':
592 x = 0;
593 // FALLTHRU
594 case 'e':
595 case 'B': {
596 if (y + ctx->esc_values[0] > ctx->rows - 1)
597 ctx->esc_values[0] = (ctx->rows - 1) - y;
598 size_t orig_y = y;
599 size_t dest_y = y + ctx->esc_values[0];
600 bool will_be_in_scroll_region = false;
601 if ((ctx->scroll_top_margin >= orig_y && ctx->scroll_top_margin <= dest_y)
602 || (ctx->scroll_bottom_margin >= orig_y && ctx->scroll_bottom_margin <= dest_y)) {
603 will_be_in_scroll_region = true;
604 }
605 if (will_be_in_scroll_region && dest_y >= ctx->scroll_bottom_margin) {
606 dest_y = ctx->scroll_bottom_margin - 1;
607 }
608 ctx->set_cursor_pos(ctx, x, dest_y);
609 break;
610 }
611 case 'a':
612 case 'C':
613 if (x + ctx->esc_values[0] > ctx->cols - 1)
614 ctx->esc_values[0] = (ctx->cols - 1) - x;
615 ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
616 break;
617 case 'D':
618 if (ctx->esc_values[0] > x)
619 ctx->esc_values[0] = x;
620 ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
621 break;
622 case 'c':
623 if (ctx->callback != NULL) {
624 ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
625 }
626 break;
627 case 'd':
628 ctx->esc_values[0] -= 1;
629 if (ctx->esc_values[0] >= ctx->rows)
630 ctx->esc_values[0] = ctx->rows - 1;
631 ctx->set_cursor_pos(ctx, x, ctx->esc_values[0]);
632 break;
633 case 'G':
634 case '`':
635 ctx->esc_values[0] -= 1;
636 if (ctx->esc_values[0] >= ctx->cols)
637 ctx->esc_values[0] = ctx->cols - 1;
638 ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
639 break;
640 case 'H':
641 case 'f':
642 if (ctx->esc_values[0] != 0) {
643 ctx->esc_values[0]--;
644 }
645 if (ctx->esc_values[1] != 0) {
646 ctx->esc_values[1]--;
647 }
648 if (ctx->esc_values[1] >= ctx->cols)
649 ctx->esc_values[1] = ctx->cols - 1;
650 if (ctx->esc_values[0] >= ctx->rows)
651 ctx->esc_values[0] = ctx->rows - 1;
652 ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0]);
653 break;
654 case 'M': {
655 size_t count = ctx->esc_values[0] > ctx->rows ? ctx->rows : ctx->esc_values[0];
656 for (size_t i = 0; i < count; i++) {
657 ctx->scroll(ctx);
658 }
659 break;
660 }
661 case 'L': {
662 size_t old_scroll_top_margin = ctx->scroll_top_margin;
663 ctx->scroll_top_margin = y;
664 size_t count = ctx->esc_values[0] > ctx->rows ? ctx->rows : ctx->esc_values[0];
665 for (size_t i = 0; i < count; i++) {
666 ctx->revscroll(ctx);
667 }
668 ctx->scroll_top_margin = old_scroll_top_margin;
669 break;
670 }
671 case 'n':
672 switch (ctx->esc_values[0]) {
673 case 5:
674 if (ctx->callback != NULL) {
675 ctx->callback(ctx, FLANTERM_CB_STATUS_REPORT, 0, 0, 0);
676 }
677 break;
678 case 6:
679 if (ctx->callback != NULL) {
680 ctx->callback(ctx, FLANTERM_CB_POS_REPORT, x + 1, y + 1, 0);
681 }
682 break;
683 }
684 break;
685 case 'q':
686 if (ctx->callback != NULL) {
687 ctx->callback(ctx, FLANTERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
688 }
689 break;
690 case 'J':
691 switch (ctx->esc_values[0]) {
692 case 0: {
693 size_t rows_remaining = ctx->rows - (y + 1);
694 size_t cols_diff = ctx->cols - (x + 1);
695 size_t to_clear = rows_remaining * ctx->cols + cols_diff + 1;
696 for (size_t i = 0; i < to_clear; i++) {
697 ctx->raw_putchar(ctx, ' ');
698 }
699 ctx->set_cursor_pos(ctx, x, y);
700 break;
701 }
702 case 1: {
703 ctx->set_cursor_pos(ctx, 0, 0);
704 bool b = false;
705 for (size_t yc = 0; yc < ctx->rows; yc++) {
706 for (size_t xc = 0; xc < ctx->cols; xc++) {
707 ctx->raw_putchar(ctx, ' ');
708 if (xc == x && yc == y) {
709 ctx->set_cursor_pos(ctx, x, y);
710 b = true;
711 break;
712 }
713 }
714 if (b == true)
715 break;
716 }
717 break;
718 }
719 case 2:
720 case 3:
721 ctx->clear(ctx, false);
722 break;
723 }
724 break;
725 case '@':
726 for (size_t i = ctx->cols - 1; ; i--) {
727 ctx->move_character(ctx, i + ctx->esc_values[0], y, i, y);
728 ctx->set_cursor_pos(ctx, i, y);
729 ctx->raw_putchar(ctx, ' ');
730 if (i == x) {
731 break;
732 }
733 }
734 ctx->set_cursor_pos(ctx, x, y);
735 break;
736 case 'P':
737 for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
738 ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
739 ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
740 // FALLTHRU
741 case 'X': {
742 size_t count = ctx->esc_values[0] > ctx->cols ? ctx->cols : ctx->esc_values[0];
743 for (size_t i = 0; i < count; i++)
744 ctx->raw_putchar(ctx, ' ');
745 ctx->set_cursor_pos(ctx, x, y);
746 break;
747 }
748 case 'm':
749 sgr(ctx);
750 break;
751 case 's':
752 ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
753 break;
754 case 'u':
755 ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
756 break;
757 case 'K':
758 switch (ctx->esc_values[0]) {
759 case 0: {
760 for (size_t i = x; i < ctx->cols; i++)
761 ctx->raw_putchar(ctx, ' ');
762 ctx->set_cursor_pos(ctx, x, y);
763 break;
764 }
765 case 1: {
766 ctx->set_cursor_pos(ctx, 0, y);
767 for (size_t i = 0; i < x; i++)
768 ctx->raw_putchar(ctx, ' ');
769 break;
770 }
771 case 2: {
772 ctx->set_cursor_pos(ctx, 0, y);
773 for (size_t i = 0; i < ctx->cols; i++)
774 ctx->raw_putchar(ctx, ' ');
775 ctx->set_cursor_pos(ctx, x, y);
776 break;
777 }
778 }
779 break;
780 case 'r':
781 if (ctx->esc_values[0] == 0) {
782 ctx->esc_values[0] = 1;
783 }
784 if (ctx->esc_values[1] == 0) {
785 ctx->esc_values[1] = 1;
786 }
787 ctx->scroll_top_margin = 0;
788 ctx->scroll_bottom_margin = ctx->rows;
789 if (ctx->esc_values_i > 0) {
790 ctx->scroll_top_margin = ctx->esc_values[0] - 1;
791 }
792 if (ctx->esc_values_i > 1) {
793 ctx->scroll_bottom_margin = ctx->esc_values[1];
794 }
795 if (ctx->scroll_top_margin >= ctx->rows
796 || ctx->scroll_bottom_margin > ctx->rows
797 || ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
798 ctx->scroll_top_margin = 0;
799 ctx->scroll_bottom_margin = ctx->rows;
800 }
801 ctx->set_cursor_pos(ctx, 0, 0);
802 break;
803 case 'l':
804 case 'h':
805 mode_toggle(ctx, c);
806 break;
807 case ']':
808 linux_private_parse(ctx);
809 break;
810 default:
811 ctx->csi_unhandled = true;
812 return;
813 }
814
815 ctx->scroll_enabled = r;
816
817cleanup:
818 ctx->control_sequence = false;
819 ctx->escape = false;
820}
821
822static void restore_state(struct flanterm_context *ctx) {
823 ctx->bold = ctx->saved_state_bold;
824 ctx->bg_bold = ctx->saved_state_bg_bold;
825 ctx->reverse_video = ctx->saved_state_reverse_video;
826 ctx->current_charset = ctx->saved_state_current_charset;
827 ctx->current_primary = ctx->saved_state_current_primary;
828 ctx->current_bg = ctx->saved_state_current_bg;
829
830 ctx->restore_state(ctx);
831}
832
833static void save_state(struct flanterm_context *ctx) {
834 ctx->save_state(ctx);
835
836 ctx->saved_state_bold = ctx->bold;
837 ctx->saved_state_bg_bold = ctx->bg_bold;
838 ctx->saved_state_reverse_video = ctx->reverse_video;
839 ctx->saved_state_current_charset = ctx->current_charset;
840 ctx->saved_state_current_primary = ctx->current_primary;
841 ctx->saved_state_current_bg = ctx->current_bg;
842}
843
844static void escape_parse(struct flanterm_context *ctx, uint8_t c) {
845 ctx->escape_offset++;
846
847 if (ctx->osc == true) {
848 // ESC \ is one of the two possible terminators of OSC sequences,
849 // so osc_parse consumes ESC.
850 // If it is then followed by \ it cleans correctly,
851 // otherwise it returns false, and it tries parsing it as another escape sequence
852 if (osc_parse(ctx, c)) {
853 return;
854 }
855 }
856
857 if (ctx->control_sequence == true) {
858 control_sequence_parse(ctx, c);
859 return;
860 }
861
862 size_t x, y;
863 ctx->get_cursor_pos(ctx, &x, &y);
864
865 switch (c) {
866 case ']':
867 ctx->osc_escape = false;
868 ctx->osc = true;
869 return;
870 case '[':
871 for (size_t i = 0; i < FLANTERM_MAX_ESC_VALUES; i++)
872 ctx->esc_values[i] = 0;
873 ctx->esc_values_i = 0;
874 ctx->rrr = false;
875 ctx->csi_unhandled = false;
876 ctx->control_sequence = true;
877 return;
878 case '7':
879 save_state(ctx);
880 break;
881 case '8':
882 restore_state(ctx);
883 break;
884 case 'c':
885 flanterm_context_reinit(ctx);
886 ctx->clear(ctx, true);
887 break;
888 case 'D':
889 if (y == ctx->scroll_bottom_margin - 1) {
890 ctx->scroll(ctx);
891 ctx->set_cursor_pos(ctx, x, y);
892 } else {
893 ctx->set_cursor_pos(ctx, x, y + 1);
894 }
895 break;
896 case 'E':
897 if (y == ctx->scroll_bottom_margin - 1) {
898 ctx->scroll(ctx);
899 ctx->set_cursor_pos(ctx, 0, y);
900 } else {
901 ctx->set_cursor_pos(ctx, 0, y + 1);
902 }
903 break;
904 case 'M':
905 // "Reverse linefeed"
906 if (y == ctx->scroll_top_margin) {
907 ctx->revscroll(ctx);
908 ctx->set_cursor_pos(ctx, 0, y);
909 } else {
910 ctx->set_cursor_pos(ctx, 0, y - 1);
911 }
912 break;
913 case 'Z':
914 if (ctx->callback != NULL) {
915 ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
916 }
917 break;
918 case '(':
919 case ')':
920 ctx->g_select = c - '\'';
921 break;
922 }
923
924 ctx->escape = false;
925}
926
927static bool dec_special_print(struct flanterm_context *ctx, uint8_t c) {
928#define FLANTERM_DEC_SPCL_PRN(C) ctx->raw_putchar(ctx, (C)); return true;
929 switch (c) {
930 case '`': FLANTERM_DEC_SPCL_PRN(0x04)
931 case '0': FLANTERM_DEC_SPCL_PRN(0xdb)
932 case '-': FLANTERM_DEC_SPCL_PRN(0x18)
933 case ',': FLANTERM_DEC_SPCL_PRN(0x1b)
934 case '.': FLANTERM_DEC_SPCL_PRN(0x19)
935 case 'a': FLANTERM_DEC_SPCL_PRN(0xb1)
936 case 'f': FLANTERM_DEC_SPCL_PRN(0xf8)
937 case 'g': FLANTERM_DEC_SPCL_PRN(0xf1)
938 case 'h': FLANTERM_DEC_SPCL_PRN(0xb0)
939 case 'j': FLANTERM_DEC_SPCL_PRN(0xd9)
940 case 'k': FLANTERM_DEC_SPCL_PRN(0xbf)
941 case 'l': FLANTERM_DEC_SPCL_PRN(0xda)
942 case 'm': FLANTERM_DEC_SPCL_PRN(0xc0)
943 case 'n': FLANTERM_DEC_SPCL_PRN(0xc5)
944 case 'q': FLANTERM_DEC_SPCL_PRN(0xc4)
945 case 's': FLANTERM_DEC_SPCL_PRN(0x5f)
946 case 't': FLANTERM_DEC_SPCL_PRN(0xc3)
947 case 'u': FLANTERM_DEC_SPCL_PRN(0xb4)
948 case 'v': FLANTERM_DEC_SPCL_PRN(0xc1)
949 case 'w': FLANTERM_DEC_SPCL_PRN(0xc2)
950 case 'x': FLANTERM_DEC_SPCL_PRN(0xb3)
951 case 'y': FLANTERM_DEC_SPCL_PRN(0xf3)
952 case 'z': FLANTERM_DEC_SPCL_PRN(0xf2)
953 case '~': FLANTERM_DEC_SPCL_PRN(0xfa)
954 case '_': FLANTERM_DEC_SPCL_PRN(0xff)
955 case '+': FLANTERM_DEC_SPCL_PRN(0x1a)
956 case '{': FLANTERM_DEC_SPCL_PRN(0xe3)
957 case '}': FLANTERM_DEC_SPCL_PRN(0x9c)
958 }
959#undef FLANTERM_DEC_SPCL_PRN
960
961 return false;
962}
963
964// Following wcwidth related code inherited from:
965// https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
966
967struct interval {
968 uint32_t first;
969 uint32_t last;
970};
971
972/* auxiliary function for binary search in interval table */
973static int bisearch(uint32_t ucs, const struct interval *table, int max) {
974 int min = 0;
975 int mid;
976
977 if (ucs < table[0].first || ucs > table[max].last)
978 return 0;
979 while (max >= min) {
980 mid = (min + max) / 2;
981 if (ucs > table[mid].last)
982 min = mid + 1;
983 else if (ucs < table[mid].first)
984 max = mid - 1;
985 else
986 return 1;
987 }
988
989 return 0;
990}
991
992int mk_wcwidth(uint32_t ucs) {
993 /* sorted list of non-overlapping intervals of non-spacing characters */
994 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
995 static const struct interval combining[] = {
996 { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
997 { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
998 { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
999 { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
1000 { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
1001 { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
1002 { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
1003 { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
1004 { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
1005 { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
1006 { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
1007 { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
1008 { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
1009 { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
1010 { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
1011 { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
1012 { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
1013 { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
1014 { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
1015 { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
1016 { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
1017 { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
1018 { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
1019 { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
1020 { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
1021 { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
1022 { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
1023 { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
1024 { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
1025 { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
1026 { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
1027 { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
1028 { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
1029 { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
1030 { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
1031 { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
1032 { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
1033 { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
1034 { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
1035 { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
1036 { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
1037 { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
1038 { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
1039 { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
1040 { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
1041 { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
1042 { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
1043 { 0xE0100, 0xE01EF }
1044 };
1045
1046 /* test for 8-bit control characters */
1047 if (ucs == 0)
1048 return 0;
1049 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
1050 return 1;
1051
1052 /* binary search in table of non-spacing characters */
1053 if (bisearch(ucs, combining,
1054 sizeof(combining) / sizeof(struct interval) - 1))
1055 return 0;
1056
1057 /* if we arrive here, ucs is not a combining or C0/C1 control character */
1058
1059 return 1 +
1060 (ucs >= 0x1100 &&
1061 (ucs <= 0x115f || /* Hangul Jamo init. consonants */
1062 ucs == 0x2329 || ucs == 0x232a ||
1063 (ucs >= 0x2e80 && ucs <= 0xa4cf &&
1064 ucs != 0x303f) || /* CJK ... Yi */
1065 (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
1066 (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
1067 (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
1068 (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
1069 (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
1070 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
1071 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
1072 (ucs >= 0x30000 && ucs <= 0x3fffd)));
1073}
1074
1075// End of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c inherited code
1076
1077static int unicode_to_cp437(uint64_t code_point) {
1078 switch (code_point) {
1079 case 0x263a: return 1;
1080 case 0x263b: return 2;
1081 case 0x2665: return 3;
1082 case 0x2666: return 4;
1083 case 0x2663: return 5;
1084 case 0x2660: return 6;
1085 case 0x2022: return 7;
1086 case 0x25d8: return 8;
1087 case 0x25cb: return 9;
1088 case 0x25d9: return 10;
1089 case 0x2642: return 11;
1090 case 0x2640: return 12;
1091 case 0x266a: return 13;
1092 case 0x266b: return 14;
1093 case 0x263c: return 15;
1094 case 0x25ba: return 16;
1095 case 0x25c4: return 17;
1096 case 0x2195: return 18;
1097 case 0x203c: return 19;
1098 case 0x00b6: return 20;
1099 case 0x00a7: return 21;
1100 case 0x25ac: return 22;
1101 case 0x21a8: return 23;
1102 case 0x2191: return 24;
1103 case 0x2193: return 25;
1104 case 0x2192: return 26;
1105 case 0x2190: return 27;
1106 case 0x221f: return 28;
1107 case 0x2194: return 29;
1108 case 0x25b2: return 30;
1109 case 0x25bc: return 31;
1110
1111 case 0x2302: return 127;
1112 case 0x00c7: return 128;
1113 case 0x00fc: return 129;
1114 case 0x00e9: return 130;
1115 case 0x00e2: return 131;
1116 case 0x00e4: return 132;
1117 case 0x00e0: return 133;
1118 case 0x00e5: return 134;
1119 case 0x00e7: return 135;
1120 case 0x00ea: return 136;
1121 case 0x00eb: return 137;
1122 case 0x00e8: return 138;
1123 case 0x00ef: return 139;
1124 case 0x00ee: return 140;
1125 case 0x00ec: return 141;
1126 case 0x00c4: return 142;
1127 case 0x00c5: return 143;
1128 case 0x00c9: return 144;
1129 case 0x00e6: return 145;
1130 case 0x00c6: return 146;
1131 case 0x00f4: return 147;
1132 case 0x00f6: return 148;
1133 case 0x00f2: return 149;
1134 case 0x00fb: return 150;
1135 case 0x00f9: return 151;
1136 case 0x00ff: return 152;
1137 case 0x00d6: return 153;
1138 case 0x00dc: return 154;
1139 case 0x00a2: return 155;
1140 case 0x00a3: return 156;
1141 case 0x00a5: return 157;
1142 case 0x20a7: return 158;
1143 case 0x0192: return 159;
1144 case 0x00e1: return 160;
1145 case 0x00ed: return 161;
1146 case 0x00f3: return 162;
1147 case 0x00fa: return 163;
1148 case 0x00f1: return 164;
1149 case 0x00d1: return 165;
1150 case 0x00aa: return 166;
1151 case 0x00ba: return 167;
1152 case 0x00bf: return 168;
1153 case 0x2310: return 169;
1154 case 0x00ac: return 170;
1155 case 0x00bd: return 171;
1156 case 0x00bc: return 172;
1157 case 0x00a1: return 173;
1158 case 0x00ab: return 174;
1159 case 0x00bb: return 175;
1160 case 0x2591: return 176;
1161 case 0x2592: return 177;
1162 case 0x2593: return 178;
1163 case 0x2502: return 179;
1164 case 0x2524: return 180;
1165 case 0x2561: return 181;
1166 case 0x2562: return 182;
1167 case 0x2556: return 183;
1168 case 0x2555: return 184;
1169 case 0x2563: return 185;
1170 case 0x2551: return 186;
1171 case 0x2557: return 187;
1172 case 0x255d: return 188;
1173 case 0x255c: return 189;
1174 case 0x255b: return 190;
1175 case 0x2510: return 191;
1176 case 0x2514: return 192;
1177 case 0x2534: return 193;
1178 case 0x252c: return 194;
1179 case 0x251c: return 195;
1180 case 0x2500: return 196;
1181 case 0x253c: return 197;
1182 case 0x255e: return 198;
1183 case 0x255f: return 199;
1184 case 0x255a: return 200;
1185 case 0x2554: return 201;
1186 case 0x2569: return 202;
1187 case 0x2566: return 203;
1188 case 0x2560: return 204;
1189 case 0x2550: return 205;
1190 case 0x256c: return 206;
1191 case 0x2567: return 207;
1192 case 0x2568: return 208;
1193 case 0x2564: return 209;
1194 case 0x2565: return 210;
1195 case 0x2559: return 211;
1196 case 0x2558: return 212;
1197 case 0x2552: return 213;
1198 case 0x2553: return 214;
1199 case 0x256b: return 215;
1200 case 0x256a: return 216;
1201 case 0x2518: return 217;
1202 case 0x250c: return 218;
1203 case 0x2588: return 219;
1204 case 0x2584: return 220;
1205 case 0x258c: return 221;
1206 case 0x2590: return 222;
1207 case 0x2580: return 223;
1208 case 0x03b1: return 224;
1209 case 0x00df: return 225;
1210 case 0x0393: return 226;
1211 case 0x03c0: return 227;
1212 case 0x03a3: return 228;
1213 case 0x03c3: return 229;
1214 case 0x00b5: return 230;
1215 case 0x03c4: return 231;
1216 case 0x03a6: return 232;
1217 case 0x0398: return 233;
1218 case 0x03a9: return 234;
1219 case 0x03b4: return 235;
1220 case 0x221e: return 236;
1221 case 0x03c6: return 237;
1222 case 0x03b5: return 238;
1223 case 0x2229: return 239;
1224 case 0x2261: return 240;
1225 case 0x00b1: return 241;
1226 case 0x2265: return 242;
1227 case 0x2264: return 243;
1228 case 0x2320: return 244;
1229 case 0x2321: return 245;
1230 case 0x00f7: return 246;
1231 case 0x2248: return 247;
1232 case 0x00b0: return 248;
1233 case 0x2219: return 249;
1234 case 0x00b7: return 250;
1235 case 0x221a: return 251;
1236 case 0x207f: return 252;
1237 case 0x00b2: return 253;
1238 case 0x25a0: return 254;
1239 }
1240
1241 return -1;
1242}
1243
1244static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c) {
1245 if (ctx->discard_next || (c == 0x18 || c == 0x1a)) {
1246 ctx->discard_next = false;
1247 ctx->escape = false;
1248 ctx->control_sequence = false;
1249 ctx->unicode_remaining = 0;
1250 ctx->osc = false;
1251 ctx->osc_escape = false;
1252 ctx->g_select = 0;
1253 return;
1254 }
1255
1256 if (ctx->unicode_remaining != 0) {
1257 if ((c & 0xc0) != 0x80) {
1258 ctx->unicode_remaining = 0;
1259 goto unicode_error;
1260 }
1261
1262 ctx->unicode_remaining--;
1263 ctx->code_point |= (uint64_t)(c & 0x3f) << (6 * ctx->unicode_remaining);
1264 if (ctx->unicode_remaining != 0) {
1265 return;
1266 }
1267
1268 int cc = unicode_to_cp437(ctx->code_point);
1269
1270 if (cc == -1) {
1271 size_t replacement_width = (size_t)mk_wcwidth(ctx->code_point);
1272 if (replacement_width > 0) {
1273 ctx->raw_putchar(ctx, 0xfe);
1274 }
1275 for (size_t i = 1; i < replacement_width; i++) {
1276 ctx->raw_putchar(ctx, ' ');
1277 }
1278 } else {
1279 ctx->raw_putchar(ctx, cc);
1280 }
1281 return;
1282 }
1283
1284unicode_error:
1285 if (c >= 0xc0 && c <= 0xf7) {
1286 if (c >= 0xc0 && c <= 0xdf) {
1287 ctx->unicode_remaining = 1;
1288 ctx->code_point = (uint64_t)(c & 0x1f) << 6;
1289 } else if (c >= 0xe0 && c <= 0xef) {
1290 ctx->unicode_remaining = 2;
1291 ctx->code_point = (uint64_t)(c & 0x0f) << (6 * 2);
1292 } else if (c >= 0xf0 && c <= 0xf7) {
1293 ctx->unicode_remaining = 3;
1294 ctx->code_point = (uint64_t)(c & 0x07) << (6 * 3);
1295 }
1296 return;
1297 }
1298
1299 if (ctx->escape == true) {
1300 escape_parse(ctx, c);
1301 return;
1302 }
1303
1304 if (ctx->g_select) {
1305 ctx->g_select--;
1306 switch (c) {
1307 case 'B':
1308 ctx->charsets[ctx->g_select] = CHARSET_DEFAULT; break;
1309 case '0':
1310 ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL; break;
1311 }
1312 ctx->g_select = 0;
1313 return;
1314 }
1315
1316 size_t x, y;
1317 ctx->get_cursor_pos(ctx, &x, &y);
1318
1319 switch (c) {
1320 case 0x00:
1321 case 0x7f:
1322 return;
1323 case 0x1b:
1324 ctx->escape_offset = 0;
1325 ctx->escape = true;
1326 return;
1327 case '\t':
1328 if ((x / ctx->tab_size + 1) >= ctx->cols) {
1329 ctx->set_cursor_pos(ctx, ctx->cols - 1, y);
1330 return;
1331 }
1332 ctx->set_cursor_pos(ctx, (x / ctx->tab_size + 1) * ctx->tab_size, y);
1333 return;
1334 case 0x0b:
1335 case 0x0c:
1336 case '\n':
1337 if (y == ctx->scroll_bottom_margin - 1) {
1338 ctx->scroll(ctx);
1339 ctx->set_cursor_pos(ctx, (ctx->oob_output & FLANTERM_OOB_OUTPUT_ONLCR) ? 0 : x, y);
1340 } else {
1341 ctx->set_cursor_pos(ctx, (ctx->oob_output & FLANTERM_OOB_OUTPUT_ONLCR) ? 0 : x, y + 1);
1342 }
1343 return;
1344 case '\b':
1345 ctx->set_cursor_pos(ctx, x - 1, y);
1346 return;
1347 case '\r':
1348 ctx->set_cursor_pos(ctx, 0, y);
1349 return;
1350 case '\a':
1351 // The bell is handled by the kernel
1352 if (ctx->callback != NULL) {
1353 ctx->callback(ctx, FLANTERM_CB_BELL, 0, 0, 0);
1354 }
1355 return;
1356 case 14:
1357 // Move to G1 set
1358 ctx->current_charset = 1;
1359 return;
1360 case 15:
1361 // Move to G0 set
1362 ctx->current_charset = 0;
1363 return;
1364 }
1365
1366 if (ctx->insert_mode == true) {
1367 for (size_t i = ctx->cols - 1; ; i--) {
1368 ctx->move_character(ctx, i + 1, y, i, y);
1369 if (i == x) {
1370 break;
1371 }
1372 }
1373 }
1374
1375 // Translate character set
1376 switch (ctx->charsets[ctx->current_charset]) {
1377 case CHARSET_DEFAULT:
1378 break;
1379 case CHARSET_DEC_SPECIAL:
1380 if (dec_special_print(ctx, c)) {
1381 return;
1382 }
1383 break;
1384 }
1385
1386 if (c >= 0x20 && c <= 0x7e) {
1387 ctx->raw_putchar(ctx, c);
1388 } else {
1389 ctx->raw_putchar(ctx, 0xfe);
1390 }
1391}
1392
1393void flanterm_flush(struct flanterm_context *ctx) {
1394 ctx->double_buffer_flush(ctx);
1395}
1396
1397void flanterm_full_refresh(struct flanterm_context *ctx) {
1398 ctx->full_refresh(ctx);
1399}
1400
1401void flanterm_deinit(struct flanterm_context *ctx, void (*_free)(void *, size_t)) {
1402 ctx->deinit(ctx, _free);
1403}
1404
1405void flanterm_get_dimensions(struct flanterm_context *ctx, size_t *cols, size_t *rows) {
1406 *cols = ctx->cols;
1407 *rows = ctx->rows;
1408}
1409
1410void flanterm_set_autoflush(struct flanterm_context *ctx, bool state) {
1411 ctx->autoflush = state;
1412}
1413
1414void flanterm_set_callback(struct flanterm_context *ctx, void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t)) {
1415 ctx->callback = callback;
1416}
1417
1418uint64_t flanterm_get_oob_output(struct flanterm_context *ctx) {
1419 return ctx->oob_output;
1420}
1421
1422void flanterm_set_oob_output(struct flanterm_context *ctx, uint64_t oob_output) {
1423 ctx->oob_output = oob_output;
1424}