1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "textcon.h" 6 7#include <assert.h> 8#include <string.h> 9#include <zircon/compiler.h> 10 11static inline void invalidate(textcon_t* tc, int x, int y, int w, int h) { 12 tc->invalidate(tc->cookie, x, y, w, h); 13} 14static inline void movecursor(textcon_t* tc, int x, int y) { 15 tc->movecursor(tc->cookie, x, y); 16} 17static inline void push_scrollback_line(textcon_t* tc, int y) { 18 tc->push_scrollback_line(tc->cookie, y); 19} 20static inline void setparam(textcon_t* tc, int param, uint8_t* arg, 21 size_t arglen) { 22 tc->setparam(tc->cookie, param, arg, arglen); 23} 24 25// Construct a vc_char_t from the given character using the current colors. 26static inline vc_char_t make_vc_char(textcon_t* tc, uint8_t ch) { 27 return (vc_char_t)(ch | 28 (((vc_char_t)tc->fg & 15) << 8) | 29 (((vc_char_t)tc->bg & 15) << 12)); 30} 31 32static vc_char_t* dataxy(textcon_t* tc, int x, int y) { 33 assert(x >= 0); 34 assert(x < tc->w); 35 assert(y >= 0); 36 assert(y < tc->h); 37 return tc->data + y * tc->w + x; 38} 39 40static vc_char_t* get_start_of_line(textcon_t* tc, int y) { 41 assert(y >= 0); 42 assert(y <= tc->h); 43 return tc->data + y * tc->w; 44} 45 46static int clampx(textcon_t* tc, int x) { 47 return x < 0 ? 0 : x >= tc->w ? tc->w - 1 : x; 48} 49 50static int clampxatedge(textcon_t* tc, int x) { 51 return x < 0 ? 0 : x > tc->w ? tc->w : x; 52} 53 54static int clampy(textcon_t* tc, int y) { 55 return y < 0 ? 0 : y >= tc->h ? tc->h - 1 : y; 56} 57 58static void moveto(textcon_t* tc, int x, int y) { 59 tc->x = clampx(tc, x); 60 tc->y = clampy(tc, y); 61} 62 63static inline void moverel(textcon_t* tc, int dx, int dy) { 64 moveto(tc, tc->x + dx, tc->y + dy); 65} 66 67static void fill(vc_char_t* ptr, vc_char_t val, size_t count) { 68 while (count-- > 0) { 69 *ptr++ = val; 70 } 71} 72 73static void erase_region(textcon_t* tc, int x0, int y0, int x1, int y1) { 74 if (x0 >= tc->w) { 75 return; 76 } 77 x1 = clampx(tc, x1); 78 vc_char_t* ptr = dataxy(tc, x0, y0); 79 vc_char_t* end = dataxy(tc, x1, y1) + 1; 80 fill(ptr, make_vc_char(tc, ' '), end - ptr); 81 invalidate(tc, x0, y0, x1 - x0 + 1, y1 - y0 + 1); 82} 83 84static void erase_screen(textcon_t* tc, int arg) { 85 switch (arg) { 86 case 0: // erase downward 87 erase_region(tc, tc->x, tc->y, tc->w - 1, tc->h - 1); 88 break; 89 case 1: // erase upward 90 erase_region(tc, 0, 0, tc->x, tc->y); 91 break; 92 case 2: // erase all 93 erase_region(tc, 0, 0, tc->w - 1, tc->h - 1); 94 break; 95 } 96} 97 98static void erase_line(textcon_t* tc, int arg) { 99 switch (arg) { 100 case 0: // erase to eol 101 erase_region(tc, tc->x, tc->y, tc->w - 1, tc->y); 102 break; 103 case 1: // erase from bol 104 erase_region(tc, 0, tc->y, tc->x, tc->y); 105 break; 106 case 2: // erase line 107 erase_region(tc, 0, tc->y, tc->w - 1, tc->y); 108 break; 109 } 110} 111 112static void erase_chars(textcon_t* tc, int arg) { 113 if (tc->x >= tc->w) { 114 return; 115 } 116 if (arg < 0) { 117 arg = 0; 118 } 119 if (arg > tc->w) { 120 arg = tc->w; 121 } 122 123 vc_char_t* dst = dataxy(tc, tc->x, tc->y); 124 vc_char_t* src = dataxy(tc, tc->x + arg, tc->y); 125 vc_char_t* end = dataxy(tc, tc->x + tc->w, tc->y); 126 127 while (src < end) { 128 *dst++ = *src++; 129 } 130 while (dst < end) { 131 *dst++ = make_vc_char(tc, ' '); 132 } 133 134 invalidate(tc, tc->x, tc->y, tc->w - tc->x, 1); 135} 136 137void tc_copy_lines(textcon_t* tc, int y_dest, int y_src, int line_count) { 138 vc_char_t* dest = get_start_of_line(tc, y_dest); 139 vc_char_t* src = get_start_of_line(tc, y_src); 140 memmove(dest, src, line_count * tc->w * sizeof(vc_char_t)); 141} 142 143static void clear_lines(textcon_t* tc, int y, int line_count) { 144 fill(get_start_of_line(tc, y), make_vc_char(tc, ' '), line_count * tc->w); 145 invalidate(tc, 0, y, tc->w, line_count); 146} 147 148// Scroll the region between line |y0| (inclusive) and |y1| (exclusive). 149// Scroll by |diff| lines, which may be positive (for moving lines up) or 150// negative (for moving lines down). 151static void scroll_lines(textcon_t* tc, int y0, int y1, int diff) { 152 int delta = diff > 0 ? diff : -diff; 153 if (delta > y1 - y0) 154 delta = y1 - y0; 155 int copy_count = y1 - y0 - delta; 156 if (diff > 0) { 157 // Scroll up. 158 for (int i = 0; i < delta; ++i) { 159 push_scrollback_line(tc, y0 + i); 160 } 161 tc->copy_lines(tc->cookie, y0, y0 + delta, copy_count); 162 clear_lines(tc, y0 + copy_count, delta); 163 } else { 164 // Scroll down. 165 tc->copy_lines(tc->cookie, y0 + delta, y0, copy_count); 166 clear_lines(tc, y0, delta); 167 } 168} 169 170static void scroll_up(textcon_t* tc) { 171 scroll_lines(tc, tc->scroll_y0, tc->scroll_y1, 1); 172} 173 174// positive = up, negative = down 175static void scroll_at_pos(textcon_t* tc, int dir) { 176 if (tc->y < tc->scroll_y0) 177 return; 178 if (tc->y >= tc->scroll_y1) 179 return; 180 181 scroll_lines(tc, tc->y, tc->scroll_y1, dir); 182} 183 184void set_scroll(textcon_t* tc, int y0, int y1) { 185 if (y0 > y1) { 186 return; 187 } 188 tc->scroll_y0 = (y0 < 0) ? 0 : y0; 189 tc->scroll_y1 = (y1 > tc->h) ? tc->h : y1; 190} 191 192static void savecursorpos(textcon_t* tc) { 193 tc->save_x = tc->x; 194 tc->save_y = tc->y; 195} 196 197static void restorecursorpos(textcon_t* tc) { 198 tc->x = clampxatedge(tc, tc->save_x); 199 tc->y = clampy(tc, tc->save_y); 200} 201 202static void putc_plain(textcon_t* tc, uint8_t c); 203static void putc_escape2(textcon_t* tc, uint8_t c); 204 205static void putc_ignore(textcon_t* tc, uint8_t c) { 206 tc->putc = putc_plain; 207} 208 209static void putc_param(textcon_t* tc, uint8_t c) { 210 switch (c) { 211 case '0': 212 case '1': 213 case '2': 214 case '3': 215 case '4': 216 case '5': 217 case '6': 218 case '7': 219 case '8': 220 case '9': 221 tc->num = tc->num * 10 + (c - '0'); 222 return; 223 case ';': 224 if (tc->argn_count < TC_MAX_ARG) { 225 tc->argn[tc->argn_count++] = tc->num; 226 } 227 tc->putc = putc_escape2; 228 break; 229 default: 230 if (tc->argn_count < TC_MAX_ARG) { 231 tc->argn[tc->argn_count++] = tc->num; 232 } 233 tc->putc = putc_escape2; 234 putc_escape2(tc, c); 235 break; 236 } 237} 238 239#define ARG0(def) ((tc->argn_count > 0) ? tc->argn[0] : (def)) 240#define ARG1(def) ((tc->argn_count > 1) ? tc->argn[1] : (def)) 241 242static void putc_dec(textcon_t* tc, uint8_t c) { 243 switch (c) { 244 case '0': 245 case '1': 246 case '2': 247 case '3': 248 case '4': 249 case '5': 250 case '6': 251 case '7': 252 case '8': 253 case '9': 254 tc->num = tc->num * 10 + (c - '0'); 255 return; 256 case 'h': 257 if (tc->num == 25) 258 setparam(tc, TC_SHOW_CURSOR, NULL, 0); 259 break; 260 case 'l': 261 if (tc->num == 25) 262 setparam(tc, TC_HIDE_CURSOR, NULL, 0); 263 break; 264 default: 265 putc_plain(tc, c); 266 break; 267 } 268 tc->putc = putc_plain; 269} 270 271static textcon_param_t osc_to_param(int osc) { 272 switch (osc) { 273 case 2: 274 return TC_SET_TITLE; 275 default: 276 return TC_INVALID; 277 } 278} 279 280static void putc_osc2(textcon_t* tc, uint8_t c) { 281 switch (c) { 282 case 7: { // end command 283 textcon_param_t param = osc_to_param(ARG0(-1)); 284 if (param != TC_INVALID && tc->argstr_size) 285 setparam(tc, param, tc->argstr, tc->argstr_size); 286 tc->putc = putc_plain; 287 break; 288 } 289 default: 290 if (tc->argstr_size < TC_MAX_ARG_LENGTH) 291 tc->argstr[tc->argstr_size++] = c; 292 break; 293 } 294} 295 296static void putc_osc(textcon_t* tc, uint8_t c) { 297 switch (c) { 298 case '0': 299 case '1': 300 case '2': 301 case '3': 302 case '4': 303 case '5': 304 case '6': 305 case '7': 306 case '8': 307 case '9': 308 tc->num = tc->num * 10 + (c - '0'); 309 return; 310 case ';': 311 if (tc->argn_count < TC_MAX_ARG) { 312 tc->argn[tc->argn_count++] = tc->num; 313 } 314 memset(tc->argstr, 0, TC_MAX_ARG_LENGTH); 315 tc->argstr_size = 0; 316 tc->putc = putc_osc2; 317 break; 318 default: 319 if (tc->argn_count < TC_MAX_ARG) { 320 tc->argn[tc->argn_count++] = tc->num; 321 } 322 tc->putc = putc_osc2; 323 putc_osc2(tc, c); 324 break; 325 } 326} 327 328static void putc_escape2(textcon_t* tc, uint8_t c) { 329 int x, y; 330 switch (c) { 331 case '0': 332 case '1': 333 case '2': 334 case '3': 335 case '4': 336 case '5': 337 case '6': 338 case '7': 339 case '8': 340 case '9': 341 tc->num = c - '0'; 342 tc->putc = putc_param; 343 return; 344 case ';': // end parameter 345 if (tc->argn_count < TC_MAX_ARG) { 346 tc->argn[tc->argn_count++] = 0; 347 } 348 return; 349 case '?': 350 tc->num = 0; 351 tc->argn_count = 0; 352 tc->putc = putc_dec; 353 return; 354 case 'A': // (CUU) Cursor Up 355 moverel(tc, 0, -ARG0(1)); 356 break; 357 case 'B': // (CUD) Cursor Down 358 moverel(tc, 0, ARG0(1)); 359 break; 360 case 'C': // (CUF) Cursor Forward 361 moverel(tc, ARG0(1), 0); 362 break; 363 case 'D': // (CUB) Cursor Backward 364 moverel(tc, -ARG0(1), 0); 365 break; 366 case 'E': 367 moveto(tc, 0, tc->y + ARG0(1)); 368 break; 369 case 'F': 370 moveto(tc, 0, tc->y - ARG0(1)); 371 break; 372 case 'G': // move xpos absolute 373 x = ARG0(1); 374 moveto(tc, x ? (x - 1) : 0, tc->y); 375 break; 376 case 'H': // (CUP) Cursor Position 377 case 'f': // (HVP) Horizontal and Vertical Position 378 x = ARG1(1); 379 y = ARG0(1); 380 moveto(tc, x ? (x - 1) : 0, y ? (y - 1) : 0); 381 break; 382 case 'J': // (ED) erase in display 383 erase_screen(tc, ARG0(0)); 384 break; 385 case 'K': // (EL) erase in line 386 erase_line(tc, ARG0(0)); 387 break; 388 case 'L': // (IL) insert line(s) at cursor 389 scroll_at_pos(tc, -ARG0(1)); 390 break; 391 case 'M': // (DL) delete line(s) at cursor 392 scroll_at_pos(tc, ARG0(1)); 393 break; 394 case 'P': // (DCH) delete character(s) 395 erase_chars(tc, ARG0(1)); 396 break; 397 case 'd': // move ypos absolute 398 y = ARG0(1); 399 moveto(tc, tc->x, y ? (y - 1) : 0); 400 break; 401 case 'm': // (SGR) Character Attributes 402 if (tc->argn_count == 0) { // no params == default param 403 tc->argn[0] = 0; 404 tc->argn_count = 1; 405 } 406 for (int i = 0; i < tc->argn_count; i++) { 407 int n = tc->argn[i]; 408 if ((n >= 30) && (n <= 37)) { // set fg 409 tc->fg = (uint8_t)(n - 30); 410 } else if ((n >= 40) && (n <= 47)) { // set bg 411 tc->bg = (uint8_t)(n - 40); 412 } else if ((n == 1) && (tc->fg <= 7)) { // bold 413 tc->fg = (uint8_t)(tc->fg + 8); 414 } else if (n == 0) { // reset 415 tc->fg = 0; 416 tc->bg = 15; 417 } else if (n == 7) { // reverse 418 uint8_t temp = tc->fg; 419 tc->fg = tc->bg; 420 tc->bg = temp; 421 } else if (n == 39) { // default fg 422 tc->fg = 0; 423 } else if (n == 49) { // default bg 424 tc->bg = 15; 425 } 426 } 427 break; 428 case 'r': // set scroll region 429 set_scroll(tc, ARG0(1) - 1, ARG1(tc->h)); 430 break; 431 case 's': // save cursor position ?? 432 savecursorpos(tc); 433 break; 434 case 'u': // restore cursor position ?? 435 restorecursorpos(tc); 436 break; 437 case '@': // (ICH) Insert Blank Character(s) 438 case 'T': // Initiate Hilight Mouse Tracking (xterm) 439 case 'c': // (DA) Send Device Attributes 440 case 'g': // (TBC) Tab Clear 441 case 'h': // (SM) Set Mode (4=Insert,20=AutoNewline) 442 case 'l': // (RM) Reset Mode (4=Replace,20=NormalLinefeed) 443 case 'n': // (DSR) Device Status Report 444 case 'x': // Request Terminal Parameters 445 default: 446 break; 447 } 448 movecursor(tc, tc->x, tc->y); 449 tc->putc = putc_plain; 450} 451 452static void putc_escape(textcon_t* tc, uint8_t c) { 453 switch (c) { 454 case 27: // escape 455 return; 456 case '(': 457 case ')': 458 case '*': 459 case '+': 460 // select various character sets 461 tc->putc = putc_ignore; 462 return; 463 case '[': 464 tc->num = 0; 465 tc->argn_count = 0; 466 tc->putc = putc_escape2; 467 return; 468 case ']': 469 tc->num = 0; 470 tc->argn_count = 0; 471 tc->putc = putc_osc; 472 return; 473 case '7': // (DECSC) Save Cursor 474 savecursorpos(tc); 475 // save attribute 476 break; 477 case '8': // (DECRC) Restore Cursor 478 restorecursorpos(tc); 479 movecursor(tc, tc->x, tc->y); 480 break; 481 case 'E': // (NEL) Next Line 482 tc->x = 0; 483 __FALLTHROUGH; 484 case 'D': // (IND) Index 485 tc->y++; 486 if (tc->y >= tc->scroll_y1) { 487 tc->y--; 488 scroll_up(tc); 489 } 490 movecursor(tc, tc->x, tc->y); 491 break; 492 case 'M': // (RI) Reverse Index) 493 tc->y--; 494 if (tc->y < tc->scroll_y0) { 495 tc->y++; 496 scroll_at_pos(tc, -1); 497 } 498 movecursor(tc, tc->x, tc->y); 499 break; 500 } 501 tc->putc = putc_plain; 502} 503 504static void putc_cr(textcon_t* tc) { 505 tc->x = 0; 506} 507 508static void putc_lf(textcon_t* tc) { 509 tc->y++; 510 if (tc->y >= tc->scroll_y1) { 511 tc->y--; 512 scroll_up(tc); 513 } 514} 515 516static void putc_plain(textcon_t* tc, uint8_t c) { 517 switch (c) { 518 case 7: // bell 519 break; 520 case 8: // backspace / ^H 521 if (tc->x > 0) 522 tc->x--; 523 break; 524 case 9: // tab / ^I 525 moveto(tc, (tc->x + 8) & (~7), tc->y); 526 break; 527 case 10: // newline 528 putc_cr(tc); // should we imply this? 529 putc_lf(tc); 530 break; 531 case 12: 532 erase_screen(tc, 2); 533 break; 534 case 13: // carriage return 535 putc_cr(tc); 536 break; 537 case 27: // escape 538 tc->putc = putc_escape; 539 return; 540 default: 541 if ((c < ' ') || (c > 127)) { 542 return; 543 } 544 if (tc->x >= tc->w) { 545 // apply deferred line wrap upon printing first character beyond 546 // end of current line 547 putc_cr(tc); 548 putc_lf(tc); 549 } 550 dataxy(tc, tc->x, tc->y)[0] = make_vc_char(tc, c); 551 invalidate(tc, tc->x, tc->y, 1, 1); 552 tc->x++; 553 break; 554 } 555 movecursor(tc, tc->x, tc->y); 556} 557 558void tc_init(textcon_t* tc, int w, int h, vc_char_t* data, 559 uint8_t fg, uint8_t bg, int cursor_x, int cursor_y) { 560 tc->w = w; 561 tc->h = h; 562 tc->x = cursor_x; 563 tc->y = cursor_y; 564 tc->data = data; 565 tc->scroll_y0 = 0; 566 tc->scroll_y1 = h; 567 tc->save_x = 0; 568 tc->save_y = 0; 569 tc->fg = fg; 570 tc->bg = bg; 571 tc->putc = putc_plain; 572} 573 574void tc_seth(textcon_t* tc, int h) { 575 // tc->data must be big enough for the new height 576 int old_h = h; 577 tc->h = h; 578 579 // move contents 580 int y = 0; 581 if (old_h > h) { 582 vc_char_t* dst = dataxy(tc, 0, tc->scroll_y0); 583 vc_char_t* src = dataxy(tc, 0, tc->scroll_y0 + old_h - h); 584 vc_char_t* end = dataxy(tc, 0, tc->scroll_y1); 585 do { 586 push_scrollback_line(tc, y); 587 } while (++y < old_h - h); 588 memmove(dst, src, (end - dst) * sizeof(vc_char_t)); 589 tc->y -= old_h - h; 590 } else if (old_h < h) { 591 do { 592 fill(dataxy(tc, 0, tc->scroll_y1 + y), make_vc_char(tc, ' '), tc->w); 593 } while (++y < h - old_h); 594 } 595 tc->y = clampy(tc, tc->y); 596 597 // try to fixup the scroll region 598 if (tc->scroll_y0 >= h) { 599 tc->scroll_y0 = 0; 600 } 601 if (tc->scroll_y1 == old_h) { 602 tc->scroll_y1 = h; 603 } else { 604 tc->scroll_y1 = tc->scroll_y1 >= h ? h : tc->scroll_y1; 605 } 606 607 invalidate(tc, 0, 0, tc->w, tc->h); 608 movecursor(tc, tc->x, tc->y); 609} 610