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