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->rrr = false;
54 ctx->discard_next = false;
55 ctx->bold = false;
56 ctx->reverse_video = false;
57 ctx->dec_private = false;
58 ctx->insert_mode = false;
59 ctx->g_select = 0;
60 ctx->charsets[0] = CHARSET_DEFAULT;
61 ctx->charsets[1] = CHARSET_DEC_SPECIAL;
62 ctx->current_charset = 0;
63 ctx->escape_offset = 0;
64 ctx->esc_values_i = 0;
65 ctx->saved_cursor_x = 0;
66 ctx->saved_cursor_y = 0;
67 ctx->current_primary = (size_t)-1;
68 ctx->scroll_top_margin = 0;
69 ctx->scroll_bottom_margin = ctx->rows;
70}
71
72static void term_putchar(struct term_context *ctx, uint8_t c);
73
74void term_write(struct term_context *ctx, const char *buf, size_t count) {
75 for (size_t i = 0; i < count; i++) {
76 term_putchar(ctx, buf[i]);
77 }
78
79 if (ctx->autoflush) {
80 ctx->double_buffer_flush(ctx);
81 }
82}
83
84static void sgr(struct term_context *ctx) {
85 size_t i = 0;
86
87 if (!ctx->esc_values_i)
88 goto def;
89
90 for (; i < ctx->esc_values_i; i++) {
91 size_t offset;
92
93 if (ctx->esc_values[i] == 0) {
94def:
95 if (ctx->reverse_video) {
96 ctx->reverse_video = false;
97 ctx->swap_palette(ctx);
98 }
99 ctx->bold = false;
100 ctx->current_primary = (size_t)-1;
101 ctx->set_text_bg_default(ctx);
102 ctx->set_text_fg_default(ctx);
103 continue;
104 }
105
106 else if (ctx->esc_values[i] == 1) {
107 ctx->bold = true;
108 if (ctx->current_primary != (size_t)-1) {
109 if (!ctx->reverse_video) {
110 ctx->set_text_fg_bright(ctx, ctx->current_primary);
111 } else {
112 ctx->set_text_bg_bright(ctx, ctx->current_primary);
113 }
114 }
115 continue;
116 }
117
118 else if (ctx->esc_values[i] == 22) {
119 ctx->bold = false;
120 if (ctx->current_primary != (size_t)-1) {
121 if (!ctx->reverse_video) {
122 ctx->set_text_fg(ctx, ctx->current_primary);
123 } else {
124 ctx->set_text_bg(ctx, ctx->current_primary);
125 }
126 }
127 continue;
128 }
129
130 else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
131 offset = 30;
132 ctx->current_primary = ctx->esc_values[i] - offset;
133
134 if (ctx->reverse_video) {
135 goto set_bg;
136 }
137
138set_fg:
139 if (ctx->bold && !ctx->reverse_video) {
140 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
141 } else {
142 ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
143 }
144 continue;
145 }
146
147 else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
148 offset = 40;
149 if (ctx->reverse_video) {
150 goto set_fg;
151 }
152
153set_bg:
154 if (ctx->bold && ctx->reverse_video) {
155 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
156 } else {
157 ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
158 }
159 continue;
160 }
161
162 else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
163 offset = 90;
164 ctx->current_primary = ctx->esc_values[i] - offset;
165
166 if (ctx->reverse_video) {
167 goto set_bg_bright;
168 }
169
170set_fg_bright:
171 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
172 continue;
173 }
174
175 else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
176 offset = 100;
177 if (ctx->reverse_video) {
178 goto set_fg_bright;
179 }
180
181set_bg_bright:
182 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
183 continue;
184 }
185
186 else if (ctx->esc_values[i] == 39) {
187 ctx->current_primary = (size_t)-1;
188
189 if (ctx->reverse_video) {
190 ctx->swap_palette(ctx);
191 }
192
193 ctx->set_text_fg_default(ctx);
194
195 if (ctx->reverse_video) {
196 ctx->swap_palette(ctx);
197 }
198
199 continue;
200 }
201
202 else if (ctx->esc_values[i] == 49) {
203 if (ctx->reverse_video) {
204 ctx->swap_palette(ctx);
205 }
206
207 ctx->set_text_bg_default(ctx);
208
209 if (ctx->reverse_video) {
210 ctx->swap_palette(ctx);
211 }
212
213 continue;
214 }
215
216 else if (ctx->esc_values[i] == 7) {
217 if (!ctx->reverse_video) {
218 ctx->reverse_video = true;
219 ctx->swap_palette(ctx);
220 }
221 continue;
222 }
223
224 else if (ctx->esc_values[i] == 27) {
225 if (ctx->reverse_video) {
226 ctx->reverse_video = false;
227 ctx->swap_palette(ctx);
228 }
229 continue;
230 }
231
232 // 256/RGB
233 else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
234 bool fg = ctx->esc_values[i] == 38;
235
236 i++;
237 if (i >= ctx->esc_values_i) {
238 break;
239 }
240
241 switch (ctx->esc_values[i]) {
242 case 2: { // RGB
243 if (i + 3 >= ctx->esc_values_i) {
244 goto out;
245 }
246
247 uint32_t rgb_value = 0;
248
249 rgb_value |= ctx->esc_values[i + 1] << 16;
250 rgb_value |= ctx->esc_values[i + 2] << 8;
251 rgb_value |= ctx->esc_values[i + 3];
252
253 i += 3;
254
255 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
256
257 break;
258 }
259 case 5: { // 256 colors
260 if (i + 1 >= ctx->esc_values_i) {
261 goto out;
262 }
263
264 uint32_t col = ctx->esc_values[i + 1];
265
266 i++;
267
268 if (col < 8) {
269 (fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
270 } else if (col < 16) {
271 (fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx, col - 8);
272 } else {
273 uint32_t rgb_value = col256[col - 16];
274 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
275 }
276
277 break;
278 }
279 default: continue;
280 }
281 }
282 }
283
284out:;
285}
286
287static void dec_private_parse(struct term_context *ctx, uint8_t c) {
288 ctx->dec_private = false;
289
290 if (ctx->esc_values_i == 0) {
291 return;
292 }
293
294 bool set;
295
296 switch (c) {
297 case 'h':
298 set = true; break;
299 case 'l':
300 set = false; break;
301 default:
302 return;
303 }
304
305 switch (ctx->esc_values[0]) {
306 case 25: {
307 if (set) {
308 ctx->enable_cursor(ctx);
309 } else {
310 ctx->disable_cursor(ctx);
311 }
312 return;
313 }
314 }
315
316 if (ctx->callback != NULL) {
317 ctx->callback(ctx, TERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
318 }
319}
320
321static void linux_private_parse(struct term_context *ctx) {
322 if (ctx->esc_values_i == 0) {
323 return;
324 }
325
326 if (ctx->callback != NULL) {
327 ctx->callback(ctx, TERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
328 }
329}
330
331static void mode_toggle(struct term_context *ctx, uint8_t c) {
332 if (ctx->esc_values_i == 0) {
333 return;
334 }
335
336 bool set;
337
338 switch (c) {
339 case 'h':
340 set = true; break;
341 case 'l':
342 set = false; break;
343 default:
344 return;
345 }
346
347 switch (ctx->esc_values[0]) {
348 case 4:
349 ctx->insert_mode = set; return;
350 }
351
352 if (ctx->callback != NULL) {
353 ctx->callback(ctx, TERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
354 }
355}
356
357static void control_sequence_parse(struct term_context *ctx, uint8_t c) {
358 if (ctx->escape_offset == 2) {
359 switch (c) {
360 case '[':
361 ctx->discard_next = true;
362 goto cleanup;
363 case '?':
364 ctx->dec_private = true;
365 return;
366 }
367 }
368
369 if (c >= '0' && c <= '9') {
370 if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
371 return;
372 }
373 ctx->rrr = true;
374 ctx->esc_values[ctx->esc_values_i] *= 10;
375 ctx->esc_values[ctx->esc_values_i] += c - '0';
376 return;
377 }
378
379 if (ctx->rrr == true) {
380 ctx->esc_values_i++;
381 ctx->rrr = false;
382 if (c == ';')
383 return;
384 } else if (c == ';') {
385 if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
386 return;
387 }
388 ctx->esc_values[ctx->esc_values_i] = 0;
389 ctx->esc_values_i++;
390 return;
391 }
392
393 size_t esc_default;
394 switch (c) {
395 case 'J': case 'K': case 'q':
396 esc_default = 0; break;
397 default:
398 esc_default = 1; break;
399 }
400
401 for (size_t i = ctx->esc_values_i; i < TERM_MAX_ESC_VALUES; i++) {
402 ctx->esc_values[i] = esc_default;
403 }
404
405 if (ctx->dec_private == true) {
406 dec_private_parse(ctx, c);
407 goto cleanup;
408 }
409
410 bool r = ctx->scroll_enabled;
411 ctx->scroll_enabled = false;
412 size_t x, y;
413 ctx->get_cursor_pos(ctx, &x, &y);
414
415 switch (c) {
416 case 'F':
417 x = 0;
418 // FALLTHRU
419 case 'A': {
420 if (ctx->esc_values[0] > y)
421 ctx->esc_values[0] = y;
422 size_t orig_y = y;
423 size_t dest_y = y - ctx->esc_values[0];
424 bool will_be_in_scroll_region = false;
425 if ((ctx->scroll_top_margin >= dest_y && ctx->scroll_top_margin <= orig_y)
426 || (ctx->scroll_bottom_margin >= dest_y && ctx->scroll_bottom_margin <= orig_y)) {
427 will_be_in_scroll_region = true;
428 }
429 if (will_be_in_scroll_region && dest_y < ctx->scroll_top_margin) {
430 dest_y = ctx->scroll_top_margin;
431 }
432 ctx->set_cursor_pos(ctx, x, dest_y);
433 break;
434 }
435 case 'E':
436 x = 0;
437 // FALLTHRU
438 case 'e':
439 case 'B': {
440 if (y + ctx->esc_values[0] > ctx->rows - 1)
441 ctx->esc_values[0] = (ctx->rows - 1) - y;
442 size_t orig_y = y;
443 size_t dest_y = y + ctx->esc_values[0];
444 bool will_be_in_scroll_region = false;
445 if ((ctx->scroll_top_margin >= orig_y && ctx->scroll_top_margin <= dest_y)
446 || (ctx->scroll_bottom_margin >= orig_y && ctx->scroll_bottom_margin <= dest_y)) {
447 will_be_in_scroll_region = true;
448 }
449 if (will_be_in_scroll_region && dest_y >= ctx->scroll_bottom_margin) {
450 dest_y = ctx->scroll_bottom_margin - 1;
451 }
452 ctx->set_cursor_pos(ctx, x, dest_y);
453 break;
454 }
455 case 'a':
456 case 'C':
457 if (x + ctx->esc_values[0] > ctx->cols - 1)
458 ctx->esc_values[0] = (ctx->cols - 1) - x;
459 ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
460 break;
461 case 'D':
462 if (ctx->esc_values[0] > x)
463 ctx->esc_values[0] = x;
464 ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
465 break;
466 case 'c':
467 if (ctx->callback != NULL) {
468 ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
469 }
470 break;
471 case 'd':
472 ctx->esc_values[0] -= 1;
473 if (ctx->esc_values[0] >= ctx->rows)
474 ctx->esc_values[0] = ctx->rows - 1;
475 ctx->set_cursor_pos(ctx, x, ctx->esc_values[0]);
476 break;
477 case 'G':
478 case '`':
479 ctx->esc_values[0] -= 1;
480 if (ctx->esc_values[0] >= ctx->cols)
481 ctx->esc_values[0] = ctx->cols - 1;
482 ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
483 break;
484 case 'H':
485 case 'f':
486 ctx->esc_values[0] -= 1;
487 ctx->esc_values[1] -= 1;
488 if (ctx->esc_values[1] >= ctx->cols)
489 ctx->esc_values[1] = ctx->cols - 1;
490 if (ctx->esc_values[0] >= ctx->rows)
491 ctx->esc_values[0] = ctx->rows - 1;
492 ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0]);
493 break;
494 case 'n':
495 switch (ctx->esc_values[0]) {
496 case 5:
497 if (ctx->callback != NULL) {
498 ctx->callback(ctx, TERM_CB_STATUS_REPORT, 0, 0, 0);
499 }
500 break;
501 case 6:
502 if (ctx->callback != NULL) {
503 ctx->callback(ctx, TERM_CB_POS_REPORT, x + 1, y + 1, 0);
504 }
505 break;
506 }
507 break;
508 case 'q':
509 if (ctx->callback != NULL) {
510 ctx->callback(ctx, TERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
511 }
512 break;
513 case 'J':
514 switch (ctx->esc_values[0]) {
515 case 0: {
516 size_t rows_remaining = ctx->rows - (y + 1);
517 size_t cols_diff = ctx->cols - (x + 1);
518 size_t to_clear = rows_remaining * ctx->cols + cols_diff;
519 for (size_t i = 0; i < to_clear; i++) {
520 ctx->raw_putchar(ctx, ' ');
521 }
522 ctx->set_cursor_pos(ctx, x, y);
523 break;
524 }
525 case 1: {
526 ctx->set_cursor_pos(ctx, 0, 0);
527 bool b = false;
528 for (size_t yc = 0; yc < ctx->rows; yc++) {
529 for (size_t xc = 0; xc < ctx->cols; xc++) {
530 ctx->raw_putchar(ctx, ' ');
531 if (xc == x && yc == y) {
532 ctx->set_cursor_pos(ctx, x, y);
533 b = true;
534 break;
535 }
536 }
537 if (b == true)
538 break;
539 }
540 break;
541 }
542 case 2:
543 case 3:
544 ctx->clear(ctx, false);
545 break;
546 }
547 break;
548 case '@':
549 for (size_t i = ctx->cols - 1; ; i--) {
550 ctx->move_character(ctx, i + ctx->esc_values[0], y, i, y);
551 ctx->set_cursor_pos(ctx, i, y);
552 ctx->raw_putchar(ctx, ' ');
553 if (i == x) {
554 break;
555 }
556 }
557 ctx->set_cursor_pos(ctx, x, y);
558 break;
559 case 'P':
560 for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
561 ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
562 ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
563 // FALLTHRU
564 case 'X':
565 for (size_t i = 0; i < ctx->esc_values[0]; i++)
566 ctx->raw_putchar(ctx, ' ');
567 ctx->set_cursor_pos(ctx, x, y);
568 break;
569 case 'm':
570 sgr(ctx);
571 break;
572 case 's':
573 ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
574 break;
575 case 'u':
576 ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
577 break;
578 case 'K':
579 switch (ctx->esc_values[0]) {
580 case 0: {
581 for (size_t i = x; i < ctx->cols; i++)
582 ctx->raw_putchar(ctx, ' ');
583 ctx->set_cursor_pos(ctx, x, y);
584 break;
585 }
586 case 1: {
587 ctx->set_cursor_pos(ctx, 0, y);
588 for (size_t i = 0; i < x; i++)
589 ctx->raw_putchar(ctx, ' ');
590 break;
591 }
592 case 2: {
593 ctx->set_cursor_pos(ctx, 0, y);
594 for (size_t i = 0; i < ctx->cols; i++)
595 ctx->raw_putchar(ctx, ' ');
596 ctx->set_cursor_pos(ctx, x, y);
597 break;
598 }
599 }
600 break;
601 case 'r':
602 ctx->scroll_top_margin = 0;
603 ctx->scroll_bottom_margin = ctx->rows;
604 if (ctx->esc_values_i > 0) {
605 ctx->scroll_top_margin = ctx->esc_values[0] - 1;
606 }
607 if (ctx->esc_values_i > 1) {
608 ctx->scroll_bottom_margin = ctx->esc_values[1];
609 }
610 if (ctx->scroll_top_margin >= ctx->rows
611 || ctx->scroll_bottom_margin > ctx->rows
612 || ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
613 ctx->scroll_top_margin = 0;
614 ctx->scroll_bottom_margin = ctx->rows;
615 }
616 ctx->set_cursor_pos(ctx, 0, 0);
617 break;
618 case 'l':
619 case 'h':
620 mode_toggle(ctx, c);
621 break;
622 case ']':
623 linux_private_parse(ctx);
624 break;
625 }
626
627 ctx->scroll_enabled = r;
628
629cleanup:
630 ctx->control_sequence = false;
631 ctx->escape = false;
632}
633
634static void restore_state(struct term_context *ctx) {
635 ctx->bold = ctx->saved_state_bold;
636 ctx->reverse_video = ctx->saved_state_reverse_video;
637 ctx->current_charset = ctx->saved_state_current_charset;
638 ctx->current_primary = ctx->saved_state_current_primary;
639
640 ctx->restore_state(ctx);
641}
642
643static void save_state(struct term_context *ctx) {
644 ctx->save_state(ctx);
645
646 ctx->saved_state_bold = ctx->bold;
647 ctx->saved_state_reverse_video = ctx->reverse_video;
648 ctx->saved_state_current_charset = ctx->current_charset;
649 ctx->saved_state_current_primary = ctx->current_primary;
650}
651
652static void escape_parse(struct term_context *ctx, uint8_t c) {
653 ctx->escape_offset++;
654
655 if (ctx->control_sequence == true) {
656 control_sequence_parse(ctx, c);
657 return;
658 }
659
660 if (ctx->csi == true) {
661 ctx->csi = false;
662 goto is_csi;
663 }
664
665 size_t x, y;
666 ctx->get_cursor_pos(ctx, &x, &y);
667
668 switch (c) {
669 case '[':
670is_csi:
671 for (size_t i = 0; i < TERM_MAX_ESC_VALUES; i++)
672 ctx->esc_values[i] = 0;
673 ctx->esc_values_i = 0;
674 ctx->rrr = false;
675 ctx->control_sequence = true;
676 return;
677 case '7':
678 save_state(ctx);
679 break;
680 case '8':
681 restore_state(ctx);
682 break;
683 case 'c':
684 term_context_reinit(ctx);
685 ctx->clear(ctx, true);
686 break;
687 case 'D':
688 if (y == ctx->scroll_bottom_margin - 1) {
689 ctx->scroll(ctx);
690 ctx->set_cursor_pos(ctx, x, y);
691 } else {
692 ctx->set_cursor_pos(ctx, x, y + 1);
693 }
694 break;
695 case 'E':
696 if (y == ctx->scroll_bottom_margin - 1) {
697 ctx->scroll(ctx);
698 ctx->set_cursor_pos(ctx, 0, y);
699 } else {
700 ctx->set_cursor_pos(ctx, 0, y + 1);
701 }
702 break;
703 case 'M':
704 // "Reverse linefeed"
705 if (y == ctx->scroll_top_margin) {
706 ctx->revscroll(ctx);
707 ctx->set_cursor_pos(ctx, 0, y);
708 } else {
709 ctx->set_cursor_pos(ctx, 0, y - 1);
710 }
711 break;
712 case 'Z':
713 if (ctx->callback != NULL) {
714 ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
715 }
716 break;
717 case '(':
718 case ')':
719 ctx->g_select = c - '\'';
720 break;
721 case '\e':
722 if (ctx->in_bootloader == true) {
723 ctx->raw_putchar(ctx, c);
724 }
725 break;
726 }
727
728 ctx->escape = false;
729}
730
731static uint8_t dec_special_to_cp437(uint8_t c) {
732 switch (c) {
733 case '`': return 0x04;
734 case '0': return 0xdb;
735 case '-': return 0x18;
736 case ',': return 0x1b;
737 case '.': return 0x19;
738 case 'a': return 0xb1;
739 case 'f': return 0xf8;
740 case 'g': return 0xf1;
741 case 'h': return 0xb0;
742 case 'j': return 0xd9;
743 case 'k': return 0xbf;
744 case 'l': return 0xda;
745 case 'm': return 0xc0;
746 case 'n': return 0xc5;
747 case 'q': return 0xc4;
748 case 's': return 0x5f;
749 case 't': return 0xc3;
750 case 'u': return 0xb4;
751 case 'v': return 0xc1;
752 case 'w': return 0xc2;
753 case 'x': return 0xb3;
754 case 'y': return 0xf3;
755 case 'z': return 0xf2;
756 case '~': return 0xfa;
757 case '_': return 0xff;
758 case '+': return 0x1a;
759 case '{': return 0xe3;
760 case '}': return 0x9c;
761 }
762
763 return c;
764}
765
766static void term_putchar(struct term_context *ctx, uint8_t c) {
767 if (ctx->discard_next || (ctx->in_bootloader == false && (c == 0x18 || c == 0x1a))) {
768 ctx->discard_next = false;
769 ctx->escape = false;
770 ctx->csi = false;
771 ctx->control_sequence = false;
772 ctx->g_select = 0;
773 return;
774 }
775
776 if (ctx->escape == true) {
777 escape_parse(ctx, c);
778 return;
779 }
780
781 if (ctx->g_select) {
782 ctx->g_select--;
783 switch (c) {
784 case 'B':
785 ctx->charsets[ctx->g_select] = CHARSET_DEFAULT; break;
786 case '0':
787 ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL; break;
788 }
789 ctx->g_select = 0;
790 return;
791 }
792
793 size_t x, y;
794 ctx->get_cursor_pos(ctx, &x, &y);
795
796 switch (c) {
797 case 0x00:
798 case 0x7f:
799 return;
800 case 0x9b:
801 ctx->csi = true;
802 // FALLTHRU
803 case '\e':
804 ctx->escape_offset = 0;
805 ctx->escape = true;
806 return;
807 case '\t':
808 if ((x / ctx->tab_size + 1) >= ctx->cols) {
809 ctx->set_cursor_pos(ctx, ctx->cols - 1, y);
810 return;
811 }
812 ctx->set_cursor_pos(ctx, (x / ctx->tab_size + 1) * ctx->tab_size, y);
813 return;
814 case 0x0b:
815 case 0x0c:
816 case '\n':
817 if (y == ctx->scroll_bottom_margin - 1) {
818 ctx->scroll(ctx);
819 ctx->set_cursor_pos(ctx, 0, y);
820 } else {
821 ctx->set_cursor_pos(ctx, 0, y + 1);
822 }
823 return;
824 case '\b':
825 ctx->set_cursor_pos(ctx, x - 1, y);
826 return;
827 case '\r':
828 ctx->set_cursor_pos(ctx, 0, y);
829 return;
830 case '\a':
831 // The bell is handled by the kernel
832 if (ctx->callback != NULL) {
833 ctx->callback(ctx, TERM_CB_BELL, 0, 0, 0);
834 }
835 return;
836 case 14:
837 // Move to G1 set
838 ctx->current_charset = 1;
839 return;
840 case 15:
841 // Move to G0 set
842 ctx->current_charset = 0;
843 return;
844 }
845
846 if (ctx->insert_mode == true) {
847 for (size_t i = ctx->cols - 1; ; i--) {
848 ctx->move_character(ctx, i + 1, y, i, y);
849 if (i == x) {
850 break;
851 }
852 }
853 }
854
855 // Translate character set
856 switch (ctx->charsets[ctx->current_charset]) {
857 case CHARSET_DEFAULT:
858 break;
859 case CHARSET_DEC_SPECIAL:
860 c = dec_special_to_cp437(c);
861 }
862
863 ctx->raw_putchar(ctx, c);
864}