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