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 0x1b: 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 0x1b: 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}