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