1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <fcntl.h> 6 #include <signal.h> 7 #include "lt_types.h" 8 #include "wchar.h" 9 10 static const char version[] = "lt_screen|v=1"; 11 12 #define ERROR_CHAR ' ' 13 #define WIDESHADOW_CHAR ((wchar)0) 14 static char const* osc8_start[] = { "\033]8;", NULL }; 15 static char const* osc8_end[] = { "\033\\", "\7", NULL }; 16 17 #define NUM_LASTCH 4 // must be >= strlen(osc8_*[*]) 18 static wchar lastch[NUM_LASTCH]; 19 static int lastch_curr = 0; 20 21 int usage(void) { 22 fprintf(stderr, "usage: lt_screen [-w width] [-h height] [-qv]\n"); 23 return 0; 24 } 25 26 // ------------------------------------------------------------------ 27 28 #define MAX_PARAMS 3 29 30 typedef struct ScreenChar { 31 wchar ch; 32 Attr attr; 33 Color fg_color; 34 Color bg_color; 35 } ScreenChar; 36 37 typedef struct ScreenState { 38 ScreenChar* chars; 39 int w; 40 int h; 41 int cx; 42 int cy; 43 Attr curr_attr; 44 Color curr_fg_color; 45 Color curr_bg_color; 46 int param_top; 47 int params[MAX_PARAMS+1]; 48 int in_esc; 49 int in_osc8; 50 } ScreenState; 51 52 static ScreenState screen; 53 static int ttyin; // input text and control sequences 54 static int ttyout; // output for screen dump 55 static int quiet = 0; 56 static int verbose = 0; 57 58 // ------------------------------------------------------------------ 59 60 // Initialize ScreenState. 61 static void screen_init(void) { 62 screen.w = 80; 63 screen.h = 24; 64 screen.cx = 0; 65 screen.cy = 0; 66 screen.in_esc = 0; 67 screen.in_osc8 = 0; 68 screen.curr_attr = 0; 69 screen.curr_fg_color = screen.curr_bg_color = NULL_COLOR; 70 screen.param_top = -1; 71 screen.params[0] = 0; 72 } 73 74 static int num_params(void) { 75 return screen.param_top+1; 76 } 77 78 static void param_print(void) { 79 int i; 80 fprintf(stderr, "("); 81 for (i = 0; i < num_params(); ++i) 82 fprintf(stderr, "%d ", screen.params[i]); 83 fprintf(stderr, ")"); 84 } 85 86 static void param_clear(void) { 87 screen.param_top = -1; 88 } 89 90 static void param_push(int v) { 91 if (screen.param_top >= (int) countof(screen.params)-1) { 92 param_clear(); 93 return; 94 } 95 screen.params[++screen.param_top] = v; 96 } 97 98 static int param_pop(void){ 99 if (num_params() == 0) 100 return -1; // missing param 101 return screen.params[screen.param_top--]; 102 } 103 104 static int screen_x(int x) { 105 if (x < 0) x = 0; 106 if (x >= screen.w) x = screen.w-1; 107 return x; 108 } 109 110 static int screen_y(int y) { 111 if (y < 0) y = 0; 112 if (y >= screen.h) y = screen.h-1; 113 return y; 114 } 115 116 // Return the char at a given screen position. 117 static ScreenChar* screen_char(int x, int y) { 118 x = screen_x(x); 119 y = screen_y(y); 120 return &screen.chars[y * screen.w + x]; 121 } 122 123 // Step the cursor after printing a char. 124 static int screen_incr(int* px, int* py) { 125 if (++(*px) >= screen.w) { 126 *px = 0; 127 if (++(*py) >= screen.h) { 128 *py = 0; 129 return 0; 130 } 131 } 132 return 1; 133 } 134 135 // Set the value, attributes and colors of a char on the screen. 136 static void screen_char_set(int x, int y, wchar ch, Attr attr, Color fg_color, Color bg_color) { 137 ScreenChar* sc = screen_char(x, y); 138 sc->ch = ch; 139 sc->attr = attr; 140 sc->fg_color = fg_color; 141 sc->bg_color = bg_color; 142 } 143 144 static int screen_clear(int x, int y, int count) { 145 while (count-- > 0) { 146 screen_char_set(x, y, '_', 0, NULL_COLOR, NULL_COLOR); 147 screen_incr(&x, &y); 148 } 149 return 1; 150 } 151 152 static void store_hex(byte** pp, int val) { 153 char hexchar[] = "0123456789ABCDEF"; 154 *(*pp)++ = hexchar[(val >> 4) & 0xf]; 155 *(*pp)++ = hexchar[val & 0xf]; 156 } 157 158 // Print an encoded image of the current screen to ttyout. 159 // The LTS_CHAR_* metachars encode changes of color and attribute. 160 static int screen_read(int x, int y, int count) { 161 Attr attr = 0; 162 int fg_color = NULL_COLOR; 163 int bg_color = NULL_COLOR; 164 while (count-- > 0) { 165 byte buf[32]; 166 byte* bufp = buf; 167 ScreenChar* sc = screen_char(x, y); 168 if (sc->attr != attr) { 169 attr = sc->attr; 170 *bufp++ = LTS_CHAR_ATTR; 171 store_hex(&bufp, attr); 172 } 173 if (sc->fg_color != fg_color) { 174 fg_color = sc->fg_color; 175 *bufp++ = LTS_CHAR_FG_COLOR; 176 store_hex(&bufp, fg_color); 177 } 178 if (sc->bg_color != bg_color) { 179 bg_color = sc->bg_color; 180 *bufp++ = LTS_CHAR_BG_COLOR; 181 store_hex(&bufp, bg_color); 182 } 183 if (x == screen.cx && y == screen.cy) 184 *bufp++ = LTS_CHAR_CURSOR; 185 if (sc->ch == '\\' || sc->ch == LTS_CHAR_ATTR || sc->ch == LTS_CHAR_FG_COLOR || sc->ch == LTS_CHAR_BG_COLOR || sc->ch == LTS_CHAR_CURSOR) 186 *bufp++ = '\\'; 187 store_wchar(&bufp, sc->ch); 188 write(ttyout, buf, bufp-buf); 189 screen_incr(&x, &y); 190 } 191 write(ttyout, "\n", 1); 192 return 1; 193 } 194 195 static int screen_move(int x, int y) { 196 screen.cx = x; 197 screen.cy = y; 198 return 1; 199 } 200 201 static int screen_cr(void) { 202 screen.cx = 0; 203 return 1; 204 } 205 206 static int screen_bs(void) { 207 if (screen.cx <= 0) return 0; 208 --screen.cx; 209 return 1; 210 } 211 212 static int screen_scroll(void) { 213 int len = screen.w * (screen.h-1); 214 memmove(screen_char(0,0), screen_char(0,1), len * sizeof(ScreenChar)); 215 screen_clear(0, screen.h-1, screen.w); 216 return 1; 217 } 218 219 static int screen_rscroll(void) { 220 int len = screen.w * (screen.h-1); 221 memmove(screen_char(0,1), screen_char(0,0), len * sizeof(ScreenChar)); 222 screen_clear(0, 0, screen.w); 223 return 1; 224 } 225 226 static int screen_set_attr(int attr) { 227 screen.curr_attr |= attr; 228 if (verbose) fprintf(stderr, "[%d,%d] set_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr); 229 return 1; 230 } 231 232 static int screen_clear_attr(int attr) { 233 screen.curr_attr &= ~attr; 234 if (verbose) fprintf(stderr, "[%d,%d] clr_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr); 235 return 1; 236 } 237 238 // ------------------------------------------------------------------ 239 // lt_screen supports certain ANSI color values. 240 // This simplifies testing SGR sequences with less -R 241 // compared to inventing custom color sequences. 242 static int screen_set_color(int color) { 243 int ret = 0; 244 switch (color) { 245 case 1: ret = screen_set_attr(ATTR_BOLD); break; 246 case 4: ret = screen_set_attr(ATTR_UNDERLINE); break; 247 case 5: 248 case 6: ret = screen_set_attr(ATTR_BLINK); break; 249 case 7: ret = screen_set_attr(ATTR_STANDOUT); break; 250 case 21: 251 case 22: ret = screen_clear_attr(ATTR_BOLD); break; 252 case 24: ret = screen_clear_attr(ATTR_UNDERLINE); break; 253 case 25: ret = screen_clear_attr(ATTR_BLINK); break; 254 case 27: ret = screen_clear_attr(ATTR_STANDOUT); break; 255 // case 38: break; 256 // case 48: break; 257 default: 258 if (color <= 0) { 259 screen.curr_fg_color = screen.curr_bg_color = NULL_COLOR; 260 screen.curr_attr = 0; 261 ret = 1; 262 } else if ((color >= 30 && color <= 37) || (color >= 90 && color <= 97)) { 263 screen.curr_fg_color = color; 264 ret = 1; 265 } else if ((color >= 40 && color <= 47) || (color >= 100 && color <= 107)) { 266 screen.curr_bg_color = color; 267 ret = 1; 268 } else { 269 fprintf(stderr, "[%d,%d] unrecognized color %d\n", screen.cx, screen.cy, color); 270 } 271 if (verbose) fprintf(stderr, "[%d,%d] set_color(%d)=%d/%d\n", screen.cx, screen.cy, color, screen.curr_fg_color, screen.curr_bg_color); 272 break; 273 } 274 return ret; 275 } 276 277 // ------------------------------------------------------------------ 278 279 static void beep(void) { 280 if (!quiet) 281 fprintf(stderr, "\7"); 282 } 283 284 // Execute an escape sequence ending with a given char. 285 static int exec_esc(wchar ch) { 286 int x, y, count; 287 if (verbose) { 288 fprintf(stderr, "exec ESC-%c ", (char)ch); 289 param_print(); 290 fprintf(stderr, "\n"); 291 } 292 switch (ch) { 293 case 'A': // clear all 294 return screen_clear(0, 0, screen.w * screen.h); 295 case 'L': // clear from cursor to end of line 296 return screen_clear(screen.cx, screen.cy, screen.w - screen.cx); 297 case 'S': // clear from cursor to end of screen 298 return screen_clear(screen.cx, screen.cy, 299 (screen.w - screen.cx) + (screen.h - screen.cy -1) * screen.w); 300 case 'R': // read N3 chars starting at (N1,N2) 301 count = param_pop(); 302 y = param_pop(); 303 x = param_pop(); 304 if (x < 0) x = 0; 305 if (y < 0) y = 0; 306 if (count < 0) count = 0; 307 return screen_read(x, y, count); 308 case 'j': // jump cursor to (N1,N2) 309 y = param_pop(); 310 x = param_pop(); 311 if (x < 0) x = 0; 312 if (y < 0) y = 0; 313 return screen_move(x, y); 314 case 'g': // visual bell 315 return 0; 316 case 'h': // cursor home 317 return screen_move(0, 0); 318 case 'l': // cursor lower left 319 return screen_move(0, screen.h-1); 320 case 'r': // reverse scroll 321 return screen_rscroll(); 322 case '<': // cursor left to start of line 323 return screen_cr(); 324 case 'e': // exit bold 325 return screen_clear_attr(ATTR_BOLD); 326 case 'b': // enter blink 327 return screen_set_attr(ATTR_BLINK); 328 case 'c': // exit blink 329 return screen_clear_attr(ATTR_BLINK); 330 case 'm': // SGR (Select Graphics Rendition) 331 if (num_params() == 0) { 332 screen_set_color(-1); 333 } else { 334 while (num_params() > 0) 335 screen_set_color(param_pop()); 336 } 337 return 0; 338 case '?': // print version string 339 write(ttyout, version, strlen(version)); 340 return 1; 341 default: 342 return 0; 343 } 344 } 345 346 // Print a char on the screen. 347 // Handles cursor movement and scrolling. 348 static int add_char(wchar ch) { 349 //if (verbose) fprintf(stderr, "add (%c) %lx at %d,%d\n", (char)ch, (long)ch, screen.cx, screen.cy); 350 screen_char_set(screen.cx, screen.cy, ch, screen.curr_attr, screen.curr_fg_color, screen.curr_bg_color); 351 int fits = 1; 352 int zero_width = (is_composing_char(ch) || 353 (screen.cx > 0 && is_combining_char(screen_char(screen.cx-1,screen.cy)->ch, ch))); 354 if (!zero_width) { 355 fits = screen_incr(&screen.cx, &screen.cy); 356 if (fits) { 357 if (is_wide_char(ch)) { 358 // The "shadow" is the second column used by a wide char. 359 screen_char_set(screen.cx, screen.cy, WIDESHADOW_CHAR, 0, NULL_COLOR, NULL_COLOR); 360 fits = screen_incr(&screen.cx, &screen.cy); 361 } else { 362 ScreenChar* sc = screen_char(screen.cx, screen.cy); 363 if (sc->ch == WIDESHADOW_CHAR) { 364 // We overwrote the first half of a wide character. 365 // Change the orphaned shadow to an error char. 366 screen_char_set(screen.cx, screen.cy, ERROR_CHAR, screen.curr_attr, NULL_COLOR, NULL_COLOR); 367 } 368 } 369 } 370 } 371 if (!fits) { // Wrap at bottom of screen = scroll 372 screen.cx = 0; 373 screen.cy = screen.h-1; 374 return screen_scroll(); 375 } 376 return 1; 377 } 378 379 // Remember the last few chars sent to the screen. 380 static void add_last(wchar ch) { 381 lastch[lastch_curr++] = ch; 382 if (lastch_curr >= NUM_LASTCH) lastch_curr = 0; 383 } 384 385 // Do the last entered characters match a string? 386 static int last_matches_str(char const* str) { 387 int ci = lastch_curr; 388 int si; 389 for (si = strlen(str)-1; si >= 0; --si) { 390 ci = (ci > 0) ? ci-1 : NUM_LASTCH-1; 391 if (str[si] != lastch[ci]) return 0; 392 } 393 return 1; 394 } 395 396 // Do the last entered characters match any one of a list of strings? 397 static int last_matches(const char* const* tbl) { 398 int ti; 399 for (ti = 0; tbl[ti] != NULL; ++ti) { 400 if (last_matches_str(tbl[ti])) 401 return 1; 402 } 403 return 0; 404 } 405 406 // Handle a char sent to the screen while it is receiving an escape sequence. 407 static int process_esc(wchar ch) { 408 int ok = 1; 409 if (screen.in_osc8) { 410 if (last_matches(osc8_end)) { 411 screen.in_osc8 = screen.in_esc = 0; 412 } else { 413 // Discard everything between osc8_start and osc8_end. 414 } 415 } else if (last_matches(osc8_start)) { 416 param_pop(); // pop the '8' 417 screen.in_osc8 = 1; 418 } else if (ch >= '0' && ch <= '9') { 419 int d = (num_params() == 0) ? 0 : screen.params[screen.param_top--]; 420 param_push(10 * d + ch - '0'); 421 } else if (ch == ';') { 422 param_push(0); 423 } else if (ch == '[' || ch == ']') { 424 ; // Ignore ANSI marker 425 } else { // end of escape sequence 426 screen.in_esc = 0; 427 ok = exec_esc(ch); 428 param_clear(); 429 } 430 return ok; 431 } 432 433 // Handle a char sent to the screen. 434 // Normally it is just printed, but some control chars are handled specially. 435 static int process_char(wchar ch) { 436 int ok = 1; 437 add_last(ch); 438 if (screen.in_esc) { 439 ok = process_esc(ch); 440 } else if (ch == ESC) { 441 screen.in_esc = 1; 442 } else if (ch == '\r') { 443 screen_cr(); 444 } else if (ch == '\b') { 445 screen_bs(); 446 } else if (ch == '\n') { 447 if (screen.cy < screen.h-1) 448 ++screen.cy; 449 else 450 screen_scroll(); 451 screen_cr(); // auto CR 452 } else if (ch == '\7') { 453 beep(); 454 } else if (ch == '\t') { 455 ok = add_char(' '); // hardware tabs not supported 456 } else if (ch >= '\40') { // printable char 457 ok = add_char(ch); 458 } 459 return ok; 460 } 461 462 // ------------------------------------------------------------------ 463 464 static int setup(int argc, char** argv) { 465 int ch; 466 screen_init(); 467 while ((ch = getopt(argc, argv, "h:qvw:")) != -1) { 468 switch (ch) { 469 case 'h': 470 screen.h = atoi(optarg); 471 break; 472 case 'q': 473 quiet = 1; 474 break; 475 case 'v': 476 ++verbose; 477 break; 478 case 'w': 479 screen.w = atoi(optarg); 480 break; 481 default: 482 return usage(); 483 } 484 } 485 int len = screen.w * screen.h; 486 screen.chars = malloc(len * sizeof(ScreenChar)); 487 screen_clear(0, 0, len); 488 if (optind >= argc) { 489 ttyin = 0; 490 ttyout = 1; 491 } else { 492 ttyin = ttyout = open(argv[optind], O_RDWR); 493 if (ttyin < 0) { 494 fprintf(stderr, "cannot open %s\n", argv[optind]); 495 return 0; 496 } 497 } 498 return 1; 499 } 500 501 static void set_signal(int signum, void (*handler)(int)) { 502 struct sigaction sa; 503 sa.sa_handler = handler; 504 sa.sa_flags = 0; 505 sigemptyset(&sa.sa_mask); 506 sigaction(signum, &sa, NULL); 507 } 508 509 int main(int argc, char** argv) { 510 set_signal(SIGINT, SIG_IGN); 511 set_signal(SIGQUIT, SIG_IGN); 512 set_signal(SIGKILL, SIG_IGN); 513 if (!setup(argc, argv)) 514 return RUN_ERR; 515 for (;;) { 516 wchar ch = read_wchar(ttyin); 517 //if (verbose) fprintf(stderr, "screen read %c (%lx)\n", pr_ascii(ch), ch); 518 if (ch == 0) 519 break; 520 if (!process_char(ch)) 521 beep(); 522 } 523 return RUN_OK; 524 } 525