st.c (66469B)
1 /* See LICENSE for license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <pwd.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <signal.h> 12 #include <sys/ioctl.h> 13 #include <sys/select.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <termios.h> 17 #include <unistd.h> 18 #include <wchar.h> 19 20 #include "st.h" 21 #include "win.h" 22 #include "graphics.h" 23 24 #if defined(__linux) 25 #include <pty.h> 26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 27 #include <util.h> 28 #elif defined(__FreeBSD__) || defined(__DragonFly__) 29 #include <libutil.h> 30 #endif 31 32 /* Arbitrary sizes */ 33 #define UTF_INVALID 0xFFFD 34 #define UTF_SIZ 4 35 #define ESC_BUF_SIZ (128*UTF_SIZ) 36 #define ESC_ARG_SIZ 16 37 #define STR_BUF_SIZ ESC_BUF_SIZ 38 #define STR_ARG_SIZ ESC_ARG_SIZ 39 #define HISTSIZE 2000 40 41 /* PUA character used as an image placeholder */ 42 #define IMAGE_PLACEHOLDER_CHAR 0x10EEEE 43 #define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE 44 45 /* macros */ 46 #define IS_SET(flag) ((term.mode & (flag)) != 0) 47 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 48 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 49 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 50 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 51 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 52 term.scr + HISTSIZE + 1) % HISTSIZE] : \ 53 term.line[(y) - term.scr]) 54 55 enum term_mode { 56 MODE_WRAP = 1 << 0, 57 MODE_INSERT = 1 << 1, 58 MODE_ALTSCREEN = 1 << 2, 59 MODE_CRLF = 1 << 3, 60 MODE_ECHO = 1 << 4, 61 MODE_PRINT = 1 << 5, 62 MODE_UTF8 = 1 << 6, 63 }; 64 65 enum cursor_movement { 66 CURSOR_SAVE, 67 CURSOR_LOAD 68 }; 69 70 enum cursor_state { 71 CURSOR_DEFAULT = 0, 72 CURSOR_WRAPNEXT = 1, 73 CURSOR_ORIGIN = 2 74 }; 75 76 enum charset { 77 CS_GRAPHIC0, 78 CS_GRAPHIC1, 79 CS_UK, 80 CS_USA, 81 CS_MULTI, 82 CS_GER, 83 CS_FIN 84 }; 85 86 enum escape_state { 87 ESC_START = 1, 88 ESC_CSI = 2, 89 ESC_STR = 4, /* DCS, OSC, PM, APC */ 90 ESC_ALTCHARSET = 8, 91 ESC_STR_END = 16, /* a final string was encountered */ 92 ESC_TEST = 32, /* Enter in test mode */ 93 ESC_UTF8 = 64, 94 }; 95 96 typedef struct { 97 Glyph attr; /* current char attributes */ 98 int x; 99 int y; 100 char state; 101 } TCursor; 102 103 typedef struct { 104 int mode; 105 int type; 106 int snap; 107 /* 108 * Selection variables: 109 * nb – normalized coordinates of the beginning of the selection 110 * ne – normalized coordinates of the end of the selection 111 * ob – original coordinates of the beginning of the selection 112 * oe – original coordinates of the end of the selection 113 */ 114 struct { 115 int x, y; 116 } nb, ne, ob, oe; 117 118 int alt; 119 } Selection; 120 121 /* Internal representation of the screen */ 122 typedef struct { 123 int row; /* nb row */ 124 int col; /* nb col */ 125 int pixw; /* width of the text area in pixels */ 126 int pixh; /* height of the text area in pixels */ 127 Line *line; /* screen */ 128 Line *alt; /* alternate screen */ 129 Line hist[HISTSIZE]; /* history buffer */ 130 int histi; /* history index */ 131 int scr; /* scroll back */ 132 int *dirty; /* dirtyness of lines */ 133 TCursor c; /* cursor */ 134 int ocx; /* old cursor col */ 135 int ocy; /* old cursor row */ 136 int top; /* top scroll limit */ 137 int bot; /* bottom scroll limit */ 138 int mode; /* terminal mode flags */ 139 int esc; /* escape state flags */ 140 char trantbl[4]; /* charset table translation */ 141 int charset; /* current charset */ 142 int icharset; /* selected charset for sequence */ 143 int *tabs; 144 Rune lastc; /* last printed char outside of sequence, 0 if control */ 145 } Term; 146 147 /* CSI Escape sequence structs */ 148 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 149 typedef struct { 150 char buf[ESC_BUF_SIZ]; /* raw string */ 151 size_t len; /* raw string length */ 152 char priv; 153 int arg[ESC_ARG_SIZ]; 154 int narg; /* nb of args */ 155 char mode[2]; 156 } CSIEscape; 157 158 /* STR Escape sequence structs */ 159 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 160 typedef struct { 161 char type; /* ESC type ... */ 162 char *buf; /* allocated raw string */ 163 size_t siz; /* allocation size */ 164 size_t len; /* raw string length */ 165 char *args[STR_ARG_SIZ]; 166 int narg; /* nb of args */ 167 } STREscape; 168 169 static void execsh(char *, char **); 170 static void stty(char **); 171 static void sigchld(int); 172 static void ttywriteraw(const char *, size_t); 173 174 static void csidump(void); 175 static void csihandle(void); 176 static void csiparse(void); 177 static void csireset(void); 178 static void osc_color_response(int, int, int); 179 static int eschandle(uchar); 180 static void strdump(void); 181 static void strhandle(void); 182 static void strparse(void); 183 static void strreset(void); 184 185 static void tprinter(char *, size_t); 186 static void tdumpsel(void); 187 static void tdumpline(int); 188 static void tdump(void); 189 static void tclearregion(int, int, int, int); 190 static void tcursor(int); 191 static void tdeletechar(int); 192 static void tdeleteline(int); 193 static void tinsertblank(int); 194 static void tinsertblankline(int); 195 static int tlinelen(int); 196 static void tmoveto(int, int); 197 static void tmoveato(int, int); 198 static void tnewline(int); 199 static void tputtab(int); 200 static void tputc(Rune); 201 static void treset(void); 202 static void tscrollup(int, int, int); 203 static void tscrolldown(int, int, int); 204 static void tsetattr(const int *, int); 205 static void tsetchar(Rune, const Glyph *, int, int); 206 static void tsetdirt(int, int); 207 static void tsetscroll(int, int); 208 static void tswapscreen(void); 209 static void tsetmode(int, int, const int *, int); 210 static int twrite(const char *, int, int); 211 static void tfulldirt(void); 212 static void tcontrolcode(uchar ); 213 static void tdectest(char ); 214 static void tdefutf8(char); 215 static int32_t tdefcolor(const int *, int *, int); 216 static void tdeftran(char); 217 static void tstrsequence(uchar); 218 219 static void drawregion(int, int, int, int); 220 221 static void selnormalize(void); 222 static void selscroll(int, int); 223 static void selsnap(int *, int *, int); 224 225 static size_t utf8decode(const char *, Rune *, size_t); 226 static Rune utf8decodebyte(char, size_t *); 227 static char utf8encodebyte(Rune, size_t); 228 static size_t utf8validate(Rune *, size_t); 229 230 static char base64dec_getc(const char **); 231 232 static ssize_t xwrite(int, const char *, size_t); 233 234 /* Globals */ 235 static Term term; 236 static Selection sel; 237 static CSIEscape csiescseq; 238 static STREscape strescseq; 239 static int iofd = 1; 240 static int cmdfd; 241 static pid_t pid; 242 243 static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 244 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 245 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 246 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 247 248 /* Converts a diacritic to a row/column/etc number. The result is 1-base, 0 249 * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */ 250 uint16_t diacritic_to_num(uint32_t code); 251 252 ssize_t 253 xwrite(int fd, const char *s, size_t len) 254 { 255 size_t aux = len; 256 ssize_t r; 257 258 while (len > 0) { 259 r = write(fd, s, len); 260 if (r < 0) 261 return r; 262 len -= r; 263 s += r; 264 } 265 266 return aux; 267 } 268 269 void * 270 xmalloc(size_t len) 271 { 272 void *p; 273 274 if (!(p = malloc(len))) 275 die("malloc: %s\n", strerror(errno)); 276 277 return p; 278 } 279 280 void * 281 xrealloc(void *p, size_t len) 282 { 283 if ((p = realloc(p, len)) == NULL) 284 die("realloc: %s\n", strerror(errno)); 285 286 return p; 287 } 288 289 char * 290 xstrdup(const char *s) 291 { 292 char *p; 293 294 if ((p = strdup(s)) == NULL) 295 die("strdup: %s\n", strerror(errno)); 296 297 return p; 298 } 299 300 size_t 301 utf8decode(const char *c, Rune *u, size_t clen) 302 { 303 size_t i, j, len, type; 304 Rune udecoded; 305 306 *u = UTF_INVALID; 307 if (!clen) 308 return 0; 309 udecoded = utf8decodebyte(c[0], &len); 310 if (!BETWEEN(len, 1, UTF_SIZ)) 311 return 1; 312 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 313 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 314 if (type != 0) 315 return j; 316 } 317 if (j < len) 318 return 0; 319 *u = udecoded; 320 utf8validate(u, len); 321 322 return len; 323 } 324 325 Rune 326 utf8decodebyte(char c, size_t *i) 327 { 328 for (*i = 0; *i < LEN(utfmask); ++(*i)) 329 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 330 return (uchar)c & ~utfmask[*i]; 331 332 return 0; 333 } 334 335 size_t 336 utf8encode(Rune u, char *c) 337 { 338 size_t len, i; 339 340 len = utf8validate(&u, 0); 341 if (len > UTF_SIZ) 342 return 0; 343 344 for (i = len - 1; i != 0; --i) { 345 c[i] = utf8encodebyte(u, 0); 346 u >>= 6; 347 } 348 c[0] = utf8encodebyte(u, len); 349 350 return len; 351 } 352 353 char 354 utf8encodebyte(Rune u, size_t i) 355 { 356 return utfbyte[i] | (u & ~utfmask[i]); 357 } 358 359 size_t 360 utf8validate(Rune *u, size_t i) 361 { 362 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 363 *u = UTF_INVALID; 364 for (i = 1; *u > utfmax[i]; ++i) 365 ; 366 367 return i; 368 } 369 370 char 371 base64dec_getc(const char **src) 372 { 373 while (**src && !isprint((unsigned char)**src)) 374 (*src)++; 375 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 376 } 377 378 char * 379 base64dec(const char *src) 380 { 381 size_t in_len = strlen(src); 382 char *result, *dst; 383 static const char base64_digits[256] = { 384 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 385 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 386 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 387 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 388 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 389 }; 390 391 if (in_len % 4) 392 in_len += 4 - (in_len % 4); 393 result = dst = xmalloc(in_len / 4 * 3 + 1); 394 while (*src) { 395 int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 396 int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 397 int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 398 int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 399 400 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 401 if (a == -1 || b == -1) 402 break; 403 404 *dst++ = (a << 2) | ((b & 0x30) >> 4); 405 if (c == -1) 406 break; 407 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 408 if (d == -1) 409 break; 410 *dst++ = ((c & 0x03) << 6) | d; 411 } 412 *dst = '\0'; 413 return result; 414 } 415 416 void 417 selinit(void) 418 { 419 sel.mode = SEL_IDLE; 420 sel.snap = 0; 421 sel.ob.x = -1; 422 } 423 424 int 425 tlinelen(int y) 426 { 427 int i = term.col; 428 429 if (TLINE(y)[i - 1].mode & ATTR_WRAP) 430 return i; 431 432 while (i > 0 && TLINE(y)[i - 1].u == ' ') 433 --i; 434 435 return i; 436 } 437 438 void 439 selstart(int col, int row, int snap) 440 { 441 selclear(); 442 sel.mode = SEL_EMPTY; 443 sel.type = SEL_REGULAR; 444 sel.alt = IS_SET(MODE_ALTSCREEN); 445 sel.snap = snap; 446 sel.oe.x = sel.ob.x = col; 447 sel.oe.y = sel.ob.y = row; 448 selnormalize(); 449 450 if (sel.snap != 0) 451 sel.mode = SEL_READY; 452 tsetdirt(sel.nb.y, sel.ne.y); 453 } 454 455 void 456 selextend(int col, int row, int type, int done) 457 { 458 int oldey, oldex, oldsby, oldsey, oldtype; 459 460 if (sel.mode == SEL_IDLE) 461 return; 462 if (done && sel.mode == SEL_EMPTY) { 463 selclear(); 464 return; 465 } 466 467 oldey = sel.oe.y; 468 oldex = sel.oe.x; 469 oldsby = sel.nb.y; 470 oldsey = sel.ne.y; 471 oldtype = sel.type; 472 473 sel.oe.x = col; 474 sel.oe.y = row; 475 selnormalize(); 476 sel.type = type; 477 478 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 479 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 480 481 sel.mode = done ? SEL_IDLE : SEL_READY; 482 } 483 484 void 485 selnormalize(void) 486 { 487 int i; 488 489 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 490 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 491 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 492 } else { 493 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 494 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 495 } 496 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 497 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 498 499 selsnap(&sel.nb.x, &sel.nb.y, -1); 500 selsnap(&sel.ne.x, &sel.ne.y, +1); 501 502 /* expand selection over line breaks */ 503 if (sel.type == SEL_RECTANGULAR) 504 return; 505 i = tlinelen(sel.nb.y); 506 if (i < sel.nb.x) 507 sel.nb.x = i; 508 if (tlinelen(sel.ne.y) <= sel.ne.x) 509 sel.ne.x = term.col - 1; 510 } 511 512 int 513 selected(int x, int y) 514 { 515 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 516 sel.alt != IS_SET(MODE_ALTSCREEN)) 517 return 0; 518 519 if (sel.type == SEL_RECTANGULAR) 520 return BETWEEN(y, sel.nb.y, sel.ne.y) 521 && BETWEEN(x, sel.nb.x, sel.ne.x); 522 523 return BETWEEN(y, sel.nb.y, sel.ne.y) 524 && (y != sel.nb.y || x >= sel.nb.x) 525 && (y != sel.ne.y || x <= sel.ne.x); 526 } 527 528 void 529 selsnap(int *x, int *y, int direction) 530 { 531 int newx, newy, xt, yt; 532 int delim, prevdelim; 533 const Glyph *gp, *prevgp; 534 535 switch (sel.snap) { 536 case SNAP_WORD: 537 /* 538 * Snap around if the word wraps around at the end or 539 * beginning of a line. 540 */ 541 prevgp = &TLINE(*y)[*x]; 542 prevdelim = ISDELIM(prevgp->u); 543 for (;;) { 544 newx = *x + direction; 545 newy = *y; 546 if (!BETWEEN(newx, 0, term.col - 1)) { 547 newy += direction; 548 newx = (newx + term.col) % term.col; 549 if (!BETWEEN(newy, 0, term.row - 1)) 550 break; 551 552 if (direction > 0) 553 yt = *y, xt = *x; 554 else 555 yt = newy, xt = newx; 556 if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 557 break; 558 } 559 560 if (newx >= tlinelen(newy)) 561 break; 562 563 gp = &TLINE(newy)[newx]; 564 delim = ISDELIM(gp->u); 565 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 566 || (delim && gp->u != prevgp->u))) 567 break; 568 569 *x = newx; 570 *y = newy; 571 prevgp = gp; 572 prevdelim = delim; 573 } 574 break; 575 case SNAP_LINE: 576 /* 577 * Snap around if the the previous line or the current one 578 * has set ATTR_WRAP at its end. Then the whole next or 579 * previous line will be selected. 580 */ 581 *x = (direction < 0) ? 0 : term.col - 1; 582 if (direction < 0) { 583 for (; *y > 0; *y += direction) { 584 if (!(TLINE(*y-1)[term.col-1].mode 585 & ATTR_WRAP)) { 586 break; 587 } 588 } 589 } else if (direction > 0) { 590 for (; *y < term.row-1; *y += direction) { 591 if (!(TLINE(*y)[term.col-1].mode 592 & ATTR_WRAP)) { 593 break; 594 } 595 } 596 } 597 break; 598 } 599 } 600 601 char * 602 getsel(void) 603 { 604 char *str, *ptr; 605 int y, bufsize, lastx, linelen; 606 const Glyph *gp, *last; 607 608 if (sel.ob.x == -1) 609 return NULL; 610 611 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 612 ptr = str = xmalloc(bufsize); 613 614 /* append every set & selected glyph to the selection */ 615 for (y = sel.nb.y; y <= sel.ne.y; y++) { 616 if ((linelen = tlinelen(y)) == 0) { 617 *ptr++ = '\n'; 618 continue; 619 } 620 621 if (sel.type == SEL_RECTANGULAR) { 622 gp = &TLINE(y)[sel.nb.x]; 623 lastx = sel.ne.x; 624 } else { 625 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 626 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 627 } 628 last = &TLINE(y)[MIN(lastx, linelen-1)]; 629 while (last >= gp && last->u == ' ') 630 --last; 631 632 for ( ; gp <= last; ++gp) { 633 if (gp->mode & ATTR_WDUMMY) 634 continue; 635 636 if (gp->mode & ATTR_IMAGE) { 637 // TODO: Copy diacritics as well 638 ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr); 639 continue; 640 } 641 642 ptr += utf8encode(gp->u, ptr); 643 } 644 645 /* 646 * Copy and pasting of line endings is inconsistent 647 * in the inconsistent terminal and GUI world. 648 * The best solution seems like to produce '\n' when 649 * something is copied from st and convert '\n' to 650 * '\r', when something to be pasted is received by 651 * st. 652 * FIXME: Fix the computer world. 653 */ 654 if ((y < sel.ne.y || lastx >= linelen) && 655 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 656 *ptr++ = '\n'; 657 } 658 *ptr = 0; 659 return str; 660 } 661 662 void 663 selclear(void) 664 { 665 if (sel.ob.x == -1) 666 return; 667 sel.mode = SEL_IDLE; 668 sel.ob.x = -1; 669 tsetdirt(sel.nb.y, sel.ne.y); 670 } 671 672 void 673 die(const char *errstr, ...) 674 { 675 va_list ap; 676 677 va_start(ap, errstr); 678 vfprintf(stderr, errstr, ap); 679 va_end(ap); 680 exit(1); 681 } 682 683 void 684 execsh(char *cmd, char **args) 685 { 686 char *sh, *prog, *arg; 687 const struct passwd *pw; 688 689 errno = 0; 690 if ((pw = getpwuid(getuid())) == NULL) { 691 if (errno) 692 die("getpwuid: %s\n", strerror(errno)); 693 else 694 die("who are you?\n"); 695 } 696 697 if ((sh = getenv("SHELL")) == NULL) 698 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 699 700 if (args) { 701 prog = args[0]; 702 arg = NULL; 703 } else if (scroll) { 704 prog = scroll; 705 arg = utmp ? utmp : sh; 706 } else if (utmp) { 707 prog = utmp; 708 arg = NULL; 709 } else { 710 prog = sh; 711 arg = NULL; 712 } 713 DEFAULT(args, ((char *[]) {prog, arg, NULL})); 714 715 unsetenv("COLUMNS"); 716 unsetenv("LINES"); 717 unsetenv("TERMCAP"); 718 setenv("LOGNAME", pw->pw_name, 1); 719 setenv("USER", pw->pw_name, 1); 720 setenv("SHELL", sh, 1); 721 setenv("HOME", pw->pw_dir, 1); 722 setenv("TERM", termname, 1); 723 724 signal(SIGCHLD, SIG_DFL); 725 signal(SIGHUP, SIG_DFL); 726 signal(SIGINT, SIG_DFL); 727 signal(SIGQUIT, SIG_DFL); 728 signal(SIGTERM, SIG_DFL); 729 signal(SIGALRM, SIG_DFL); 730 731 execvp(prog, args); 732 _exit(1); 733 } 734 735 void 736 sigchld(int a) 737 { 738 int stat; 739 pid_t p; 740 741 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 742 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 743 744 if (pid != p) 745 return; 746 747 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 748 die("child exited with status %d\n", WEXITSTATUS(stat)); 749 else if (WIFSIGNALED(stat)) 750 die("child terminated due to signal %d\n", WTERMSIG(stat)); 751 _exit(0); 752 } 753 754 void 755 stty(char **args) 756 { 757 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 758 size_t n, siz; 759 760 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 761 die("incorrect stty parameters\n"); 762 memcpy(cmd, stty_args, n); 763 q = cmd + n; 764 siz = sizeof(cmd) - n; 765 for (p = args; p && (s = *p); ++p) { 766 if ((n = strlen(s)) > siz-1) 767 die("stty parameter length too long\n"); 768 *q++ = ' '; 769 memcpy(q, s, n); 770 q += n; 771 siz -= n + 1; 772 } 773 *q = '\0'; 774 if (system(cmd) != 0) 775 perror("Couldn't call stty"); 776 } 777 778 int 779 ttynew(const char *line, char *cmd, const char *out, char **args) 780 { 781 int m, s; 782 783 if (out) { 784 term.mode |= MODE_PRINT; 785 iofd = (!strcmp(out, "-")) ? 786 1 : open(out, O_WRONLY | O_CREAT, 0666); 787 if (iofd < 0) { 788 fprintf(stderr, "Error opening %s:%s\n", 789 out, strerror(errno)); 790 } 791 } 792 793 if (line) { 794 if ((cmdfd = open(line, O_RDWR)) < 0) 795 die("open line '%s' failed: %s\n", 796 line, strerror(errno)); 797 dup2(cmdfd, 0); 798 stty(args); 799 return cmdfd; 800 } 801 802 /* seems to work fine on linux, openbsd and freebsd */ 803 if (openpty(&m, &s, NULL, NULL, NULL) < 0) 804 die("openpty failed: %s\n", strerror(errno)); 805 806 switch (pid = fork()) { 807 case -1: 808 die("fork failed: %s\n", strerror(errno)); 809 break; 810 case 0: 811 close(iofd); 812 close(m); 813 setsid(); /* create a new process group */ 814 dup2(s, 0); 815 dup2(s, 1); 816 dup2(s, 2); 817 if (ioctl(s, TIOCSCTTY, NULL) < 0) 818 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 819 if (s > 2) 820 close(s); 821 #ifdef __OpenBSD__ 822 if (pledge("stdio getpw proc exec", NULL) == -1) 823 die("pledge\n"); 824 #endif 825 execsh(cmd, args); 826 break; 827 default: 828 #ifdef __OpenBSD__ 829 if (pledge("stdio rpath tty proc", NULL) == -1) 830 die("pledge\n"); 831 #endif 832 close(s); 833 cmdfd = m; 834 signal(SIGCHLD, sigchld); 835 break; 836 } 837 return cmdfd; 838 } 839 840 size_t 841 ttyread(void) 842 { 843 static char buf[BUFSIZ]; 844 static int buflen = 0; 845 static int already_processing = 0; 846 int ret, written = 0; 847 848 if (buflen >= LEN(buf)) 849 return 0; 850 851 /* append read bytes to unprocessed bytes */ 852 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 853 854 switch (ret) { 855 case 0: 856 exit(0); 857 case -1: 858 die("couldn't read from shell: %s\n", strerror(errno)); 859 default: 860 buflen += ret; 861 if (already_processing) { 862 /* Avoid recursive call to twrite() */ 863 return ret; 864 } 865 already_processing = 1; 866 while (1) { 867 int buflen_before_processing = buflen; 868 written += twrite(buf + written, buflen - written, 0); 869 // If buflen changed during the call to twrite, there is 870 // new data, and we need to keep processing, otherwise 871 // we can exit. This will not loop forever because the 872 // buffer is limited, and we don't clean it in this 873 // loop, so at some point ttywrite will have to drop 874 // some data. 875 if (buflen_before_processing == buflen) 876 break; 877 } 878 already_processing = 0; 879 buflen -= written; 880 /* keep any incomplete UTF-8 byte sequence for the next call */ 881 if (buflen > 0) 882 memmove(buf, buf + written, buflen); 883 return ret; 884 } 885 } 886 887 void 888 ttywrite(const char *s, size_t n, int may_echo) 889 { 890 const char *next; 891 Arg arg = (Arg) { .i = term.scr }; 892 893 kscrolldown(&arg); 894 895 if (may_echo && IS_SET(MODE_ECHO)) 896 twrite(s, n, 1); 897 898 if (!IS_SET(MODE_CRLF)) { 899 ttywriteraw(s, n); 900 return; 901 } 902 903 /* This is similar to how the kernel handles ONLCR for ttys */ 904 while (n > 0) { 905 if (*s == '\r') { 906 next = s + 1; 907 ttywriteraw("\r\n", 2); 908 } else { 909 next = memchr(s, '\r', n); 910 DEFAULT(next, s + n); 911 ttywriteraw(s, next - s); 912 } 913 n -= next - s; 914 s = next; 915 } 916 } 917 918 void 919 ttywriteraw(const char *s, size_t n) 920 { 921 fd_set wfd, rfd; 922 ssize_t r; 923 size_t lim = 256; 924 int retries_left = 100; 925 926 /* 927 * Remember that we are using a pty, which might be a modem line. 928 * Writing too much will clog the line. That's why we are doing this 929 * dance. 930 * FIXME: Migrate the world to Plan 9. 931 */ 932 while (n > 0) { 933 if (retries_left-- <= 0) 934 goto too_many_retries; 935 936 FD_ZERO(&wfd); 937 FD_ZERO(&rfd); 938 FD_SET(cmdfd, &wfd); 939 FD_SET(cmdfd, &rfd); 940 941 /* Check if we can write. */ 942 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 943 if (errno == EINTR) 944 continue; 945 die("select failed: %s\n", strerror(errno)); 946 } 947 if (FD_ISSET(cmdfd, &wfd)) { 948 /* 949 * Only write the bytes written by ttywrite() or the 950 * default of 256. This seems to be a reasonable value 951 * for a serial line. Bigger values might clog the I/O. 952 */ 953 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 954 goto write_error; 955 if (r < n) { 956 /* 957 * We weren't able to write out everything. 958 * This means the buffer is getting full 959 * again. Empty it. 960 */ 961 if (n < lim) 962 lim = ttyread(); 963 n -= r; 964 s += r; 965 } else { 966 /* All bytes have been written. */ 967 break; 968 } 969 } 970 if (FD_ISSET(cmdfd, &rfd)) 971 lim = ttyread(); 972 } 973 return; 974 975 write_error: 976 die("write error on tty: %s\n", strerror(errno)); 977 too_many_retries: 978 fprintf(stderr, "Could not write %zu bytes to tty\n", n); 979 } 980 981 void 982 ttyresize(int tw, int th) 983 { 984 term.pixw = tw; 985 term.pixh = th; 986 987 struct winsize w; 988 989 w.ws_row = term.row; 990 w.ws_col = term.col; 991 w.ws_xpixel = tw; 992 w.ws_ypixel = th; 993 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 994 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 995 } 996 997 void 998 ttyhangup(void) 999 { 1000 /* Send SIGHUP to shell */ 1001 kill(pid, SIGHUP); 1002 } 1003 1004 int 1005 tattrset(int attr) 1006 { 1007 int i, j; 1008 1009 for (i = 0; i < term.row-1; i++) { 1010 for (j = 0; j < term.col-1; j++) { 1011 if (term.line[i][j].mode & attr) 1012 return 1; 1013 } 1014 } 1015 1016 return 0; 1017 } 1018 1019 void 1020 tsetdirt(int top, int bot) 1021 { 1022 int i; 1023 1024 LIMIT(top, 0, term.row-1); 1025 LIMIT(bot, 0, term.row-1); 1026 1027 for (i = top; i <= bot; i++) 1028 term.dirty[i] = 1; 1029 } 1030 1031 void 1032 tsetdirtattr(int attr) 1033 { 1034 int i, j; 1035 1036 for (i = 0; i < term.row-1; i++) { 1037 for (j = 0; j < term.col-1; j++) { 1038 if (term.line[i][j].mode & attr) { 1039 tsetdirt(i, i); 1040 break; 1041 } 1042 } 1043 } 1044 } 1045 1046 void 1047 tfulldirt(void) 1048 { 1049 tsetdirt(0, term.row-1); 1050 } 1051 1052 void 1053 tcursor(int mode) 1054 { 1055 static TCursor c[2]; 1056 int alt = IS_SET(MODE_ALTSCREEN); 1057 1058 if (mode == CURSOR_SAVE) { 1059 c[alt] = term.c; 1060 } else if (mode == CURSOR_LOAD) { 1061 term.c = c[alt]; 1062 tmoveto(c[alt].x, c[alt].y); 1063 } 1064 } 1065 1066 void 1067 treset(void) 1068 { 1069 uint i; 1070 1071 term.c = (TCursor){{ 1072 .mode = ATTR_NULL, 1073 .fg = defaultfg, 1074 .bg = defaultbg, 1075 .decor = DECOR_DEFAULT_COLOR 1076 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1077 1078 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1079 for (i = tabspaces; i < term.col; i += tabspaces) 1080 term.tabs[i] = 1; 1081 term.top = 0; 1082 term.bot = term.row - 1; 1083 term.mode = MODE_WRAP|MODE_UTF8; 1084 memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1085 term.charset = 0; 1086 1087 for (i = 0; i < 2; i++) { 1088 tmoveto(0, 0); 1089 tcursor(CURSOR_SAVE); 1090 tclearregion(0, 0, term.col-1, term.row-1); 1091 tswapscreen(); 1092 } 1093 } 1094 1095 void 1096 tnew(int col, int row) 1097 { 1098 term = (Term){.c = {.attr = {.fg = defaultfg, 1099 .bg = defaultbg, 1100 .decor = DECOR_DEFAULT_COLOR}}}; 1101 tresize(col, row); 1102 treset(); 1103 } 1104 1105 void 1106 tswapscreen(void) 1107 { 1108 Line *tmp = term.line; 1109 1110 term.line = term.alt; 1111 term.alt = tmp; 1112 term.mode ^= MODE_ALTSCREEN; 1113 tfulldirt(); 1114 } 1115 1116 void 1117 kscrolldown(const Arg* a) 1118 { 1119 int n = a->i; 1120 1121 if (n < 0) 1122 n = term.row + n; 1123 1124 if (n > term.scr) 1125 n = term.scr; 1126 1127 if (term.scr > 0) { 1128 term.scr -= n; 1129 selscroll(0, -n); 1130 tfulldirt(); 1131 } 1132 } 1133 1134 void 1135 kscrollup(const Arg* a) 1136 { 1137 int n = a->i; 1138 1139 if (n < 0) 1140 n = term.row + n; 1141 1142 if (term.scr <= HISTSIZE-n) { 1143 term.scr += n; 1144 selscroll(0, n); 1145 tfulldirt(); 1146 } 1147 } 1148 1149 void 1150 tscrolldown(int orig, int n, int copyhist) 1151 { 1152 int i; 1153 Line temp; 1154 1155 LIMIT(n, 0, term.bot-orig+1); 1156 1157 if (copyhist) { 1158 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1159 temp = term.hist[term.histi]; 1160 term.hist[term.histi] = term.line[term.bot]; 1161 term.line[term.bot] = temp; 1162 } 1163 1164 tsetdirt(orig, term.bot-n); 1165 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1166 1167 for (i = term.bot; i >= orig+n; i--) { 1168 temp = term.line[i]; 1169 term.line[i] = term.line[i-n]; 1170 term.line[i-n] = temp; 1171 } 1172 1173 if (term.scr == 0) 1174 selscroll(orig, n); 1175 } 1176 1177 void 1178 tscrollup(int orig, int n, int copyhist) 1179 { 1180 int i; 1181 Line temp; 1182 1183 LIMIT(n, 0, term.bot-orig+1); 1184 1185 if (copyhist) { 1186 term.histi = (term.histi + 1) % HISTSIZE; 1187 temp = term.hist[term.histi]; 1188 term.hist[term.histi] = term.line[orig]; 1189 term.line[orig] = temp; 1190 } 1191 1192 if (term.scr > 0 && term.scr < HISTSIZE) 1193 term.scr = MIN(term.scr + n, HISTSIZE-1); 1194 1195 tclearregion(0, orig, term.col-1, orig+n-1); 1196 tsetdirt(orig+n, term.bot); 1197 1198 for (i = orig; i <= term.bot-n; i++) { 1199 temp = term.line[i]; 1200 term.line[i] = term.line[i+n]; 1201 term.line[i+n] = temp; 1202 } 1203 1204 if (term.scr == 0) 1205 selscroll(orig, -n); 1206 } 1207 1208 void 1209 selscroll(int orig, int n) 1210 { 1211 if (sel.ob.x == -1) 1212 return; 1213 1214 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1215 selclear(); 1216 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1217 sel.ob.y += n; 1218 sel.oe.y += n; 1219 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1220 sel.oe.y < term.top || sel.oe.y > term.bot) { 1221 selclear(); 1222 } else { 1223 selnormalize(); 1224 } 1225 } 1226 } 1227 1228 void 1229 tnewline(int first_col) 1230 { 1231 int y = term.c.y; 1232 1233 if (y == term.bot) { 1234 tscrollup(term.top, 1, 1); 1235 } else { 1236 y++; 1237 } 1238 tmoveto(first_col ? 0 : term.c.x, y); 1239 } 1240 1241 void 1242 csiparse(void) 1243 { 1244 char *p = csiescseq.buf, *np; 1245 long int v; 1246 1247 csiescseq.narg = 0; 1248 if (*p == '?') { 1249 csiescseq.priv = 1; 1250 p++; 1251 } 1252 1253 csiescseq.buf[csiescseq.len] = '\0'; 1254 while (p < csiescseq.buf+csiescseq.len) { 1255 np = NULL; 1256 v = strtol(p, &np, 10); 1257 if (np == p) 1258 v = 0; 1259 if (v == LONG_MAX || v == LONG_MIN) 1260 v = -1; 1261 csiescseq.arg[csiescseq.narg++] = v; 1262 p = np; 1263 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1264 break; 1265 p++; 1266 } 1267 csiescseq.mode[0] = *p++; 1268 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1269 } 1270 1271 /* for absolute user moves, when decom is set */ 1272 void 1273 tmoveato(int x, int y) 1274 { 1275 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1276 } 1277 1278 void 1279 tmoveto(int x, int y) 1280 { 1281 int miny, maxy; 1282 1283 if (term.c.state & CURSOR_ORIGIN) { 1284 miny = term.top; 1285 maxy = term.bot; 1286 } else { 1287 miny = 0; 1288 maxy = term.row - 1; 1289 } 1290 term.c.state &= ~CURSOR_WRAPNEXT; 1291 term.c.x = LIMIT(x, 0, term.col-1); 1292 term.c.y = LIMIT(y, miny, maxy); 1293 } 1294 1295 void 1296 tsetchar(Rune u, const Glyph *attr, int x, int y) 1297 { 1298 static const char *vt100_0[62] = { /* 0x41 - 0x7e */ 1299 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1300 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1301 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1302 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1303 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1304 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1305 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1306 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1307 }; 1308 1309 /* 1310 * The table is proudly stolen from rxvt. 1311 */ 1312 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1313 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1314 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1315 1316 if (term.line[y][x].mode & ATTR_WIDE) { 1317 if (x+1 < term.col) { 1318 term.line[y][x+1].u = ' '; 1319 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1320 } 1321 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1322 term.line[y][x-1].u = ' '; 1323 term.line[y][x-1].mode &= ~ATTR_WIDE; 1324 } 1325 1326 if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE && 1327 tgetisclassicplaceholder(&term.line[y][x])) { 1328 // This is a workaround: don't overwrite classic placement 1329 // placeholders with space symbols (unlike Unicode placeholders 1330 // which must be overwritten by anything). 1331 term.line[y][x].bg = attr->bg; 1332 term.dirty[y] = 1; 1333 return; 1334 } 1335 1336 term.dirty[y] = 1; 1337 term.line[y][x] = *attr; 1338 term.line[y][x].u = u; 1339 1340 if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) { 1341 term.line[y][x].u = 0; 1342 term.line[y][x].mode |= ATTR_IMAGE; 1343 } 1344 } 1345 1346 void 1347 tclearregion(int x1, int y1, int x2, int y2) 1348 { 1349 int x, y, temp; 1350 Glyph *gp; 1351 1352 if (x1 > x2) 1353 temp = x1, x1 = x2, x2 = temp; 1354 if (y1 > y2) 1355 temp = y1, y1 = y2, y2 = temp; 1356 1357 LIMIT(x1, 0, term.col-1); 1358 LIMIT(x2, 0, term.col-1); 1359 LIMIT(y1, 0, term.row-1); 1360 LIMIT(y2, 0, term.row-1); 1361 1362 for (y = y1; y <= y2; y++) { 1363 term.dirty[y] = 1; 1364 for (x = x1; x <= x2; x++) { 1365 gp = &term.line[y][x]; 1366 if (selected(x, y)) 1367 selclear(); 1368 gp->fg = term.c.attr.fg; 1369 gp->bg = term.c.attr.bg; 1370 gp->decor = term.c.attr.decor; 1371 gp->mode = 0; 1372 gp->u = ' '; 1373 } 1374 } 1375 } 1376 1377 /// Fills a rectangle area with an image placeholder. The starting point is the 1378 /// cursor. Adds empty lines if needed. The placeholder will be marked as 1379 /// classic. 1380 void 1381 tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, 1382 int cols, int rows, char do_not_move_cursor) 1383 { 1384 for (int row = 0; row < rows; ++row) { 1385 int y = term.c.y; 1386 term.dirty[y] = 1; 1387 for (int col = 0; col < cols; ++col) { 1388 int x = term.c.x + col; 1389 if (x >= term.col) 1390 break; 1391 Glyph *gp = &term.line[y][x]; 1392 if (selected(x, y)) 1393 selclear(); 1394 gp->mode = ATTR_IMAGE; 1395 gp->u = 0; 1396 tsetimgrow(gp, row + 1); 1397 tsetimgcol(gp, col + 1); 1398 tsetimgid(gp, image_id); 1399 tsetimgplacementid(gp, placement_id); 1400 tsetimgdiacriticcount(gp, 3); 1401 tsetisclassicplaceholder(gp, 1); 1402 } 1403 // If moving the cursor is not allowed and this is the last line 1404 // of the terminal, we are done. 1405 if (do_not_move_cursor && y == term.row - 1) 1406 break; 1407 // Move the cursor down, maybe creating a new line. The x is 1408 // preserved (we never change term.c.x in the loop above). 1409 if (row != rows - 1) 1410 tnewline(/*first_col=*/0); 1411 } 1412 if (do_not_move_cursor) { 1413 // Return the cursor to the original position. 1414 tmoveto(term.c.x, term.c.y - rows + 1); 1415 } else { 1416 // Move the cursor beyond the last column, as required by the 1417 // protocol. If the cursor goes beyond the screen edge, insert a 1418 // newline to match the behavior of kitty. 1419 if (term.c.x + cols >= term.col) 1420 tnewline(/*first_col=*/1); 1421 else 1422 tmoveto(term.c.x + cols, term.c.y); 1423 } 1424 } 1425 1426 void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, 1427 uint32_t placement_id, int col, 1428 int row, char is_classic), 1429 void *data) { 1430 for (int row = 0; row < term.row; ++row) { 1431 for (int col = 0; col < term.col; ++col) { 1432 Glyph *gp = &term.line[row][col]; 1433 if (gp->mode & ATTR_IMAGE) { 1434 uint32_t image_id = tgetimgid(gp); 1435 uint32_t placement_id = tgetimgplacementid(gp); 1436 int ret = 1437 callback(data, tgetimgid(gp), 1438 tgetimgplacementid(gp), 1439 tgetimgcol(gp), tgetimgrow(gp), 1440 tgetisclassicplaceholder(gp)); 1441 if (ret == 1) { 1442 term.dirty[row] = 1; 1443 gp->mode = 0; 1444 gp->u = ' '; 1445 } 1446 } 1447 } 1448 } 1449 } 1450 1451 void gr_schedule_image_redraw_by_id(uint32_t image_id) { 1452 for (int row = 0; row < term.row; ++row) { 1453 if (term.dirty[row]) 1454 continue; 1455 for (int col = 0; col < term.col; ++col) { 1456 Glyph *gp = &term.line[row][col]; 1457 if (gp->mode & ATTR_IMAGE) { 1458 uint32_t cell_image_id = tgetimgid(gp); 1459 if (cell_image_id == image_id) { 1460 term.dirty[row] = 1; 1461 break; 1462 } 1463 } 1464 } 1465 } 1466 } 1467 1468 void 1469 tdeletechar(int n) 1470 { 1471 int dst, src, size; 1472 Glyph *line; 1473 1474 LIMIT(n, 0, term.col - term.c.x); 1475 1476 dst = term.c.x; 1477 src = term.c.x + n; 1478 size = term.col - src; 1479 line = term.line[term.c.y]; 1480 1481 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1482 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1483 } 1484 1485 void 1486 tinsertblank(int n) 1487 { 1488 int dst, src, size; 1489 Glyph *line; 1490 1491 LIMIT(n, 0, term.col - term.c.x); 1492 1493 dst = term.c.x + n; 1494 src = term.c.x; 1495 size = term.col - dst; 1496 line = term.line[term.c.y]; 1497 1498 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1499 tclearregion(src, term.c.y, dst - 1, term.c.y); 1500 } 1501 1502 void 1503 tinsertblankline(int n) 1504 { 1505 if (BETWEEN(term.c.y, term.top, term.bot)) 1506 tscrolldown(term.c.y, n, 0); 1507 } 1508 1509 void 1510 tdeleteline(int n) 1511 { 1512 if (BETWEEN(term.c.y, term.top, term.bot)) 1513 tscrollup(term.c.y, n, 0); 1514 } 1515 1516 int32_t 1517 tdefcolor(const int *attr, int *npar, int l) 1518 { 1519 int32_t idx = -1; 1520 uint r, g, b; 1521 1522 switch (attr[*npar + 1]) { 1523 case 2: /* direct color in RGB space */ 1524 if (*npar + 4 >= l) { 1525 fprintf(stderr, 1526 "erresc(38): Incorrect number of parameters (%d)\n", 1527 *npar); 1528 break; 1529 } 1530 r = attr[*npar + 2]; 1531 g = attr[*npar + 3]; 1532 b = attr[*npar + 4]; 1533 *npar += 4; 1534 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1535 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1536 r, g, b); 1537 else 1538 idx = TRUECOLOR(r, g, b); 1539 break; 1540 case 5: /* indexed color */ 1541 if (*npar + 2 >= l) { 1542 fprintf(stderr, 1543 "erresc(38): Incorrect number of parameters (%d)\n", 1544 *npar); 1545 break; 1546 } 1547 *npar += 2; 1548 if (!BETWEEN(attr[*npar], 0, 255)) 1549 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1550 else 1551 idx = attr[*npar]; 1552 break; 1553 case 0: /* implemented defined (only foreground) */ 1554 case 1: /* transparent */ 1555 case 3: /* direct color in CMY space */ 1556 case 4: /* direct color in CMYK space */ 1557 default: 1558 fprintf(stderr, 1559 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1560 break; 1561 } 1562 1563 return idx; 1564 } 1565 1566 void 1567 tsetattr(const int *attr, int l) 1568 { 1569 int i; 1570 int32_t idx; 1571 1572 for (i = 0; i < l; i++) { 1573 switch (attr[i]) { 1574 case 0: 1575 term.c.attr.mode &= ~( 1576 ATTR_BOLD | 1577 ATTR_FAINT | 1578 ATTR_ITALIC | 1579 ATTR_UNDERLINE | 1580 ATTR_BLINK | 1581 ATTR_REVERSE | 1582 ATTR_INVISIBLE | 1583 ATTR_STRUCK ); 1584 term.c.attr.fg = defaultfg; 1585 term.c.attr.bg = defaultbg; 1586 term.c.attr.decor = DECOR_DEFAULT_COLOR; 1587 break; 1588 case 1: 1589 term.c.attr.mode |= ATTR_BOLD; 1590 break; 1591 case 2: 1592 term.c.attr.mode |= ATTR_FAINT; 1593 break; 1594 case 3: 1595 term.c.attr.mode |= ATTR_ITALIC; 1596 break; 1597 case 4: 1598 term.c.attr.mode |= ATTR_UNDERLINE; 1599 if (i + 1 < l) { 1600 idx = attr[++i]; 1601 if (BETWEEN(idx, 1, 5)) { 1602 tsetdecorstyle(&term.c.attr, idx); 1603 } else if (idx == 0) { 1604 term.c.attr.mode &= ~ATTR_UNDERLINE; 1605 tsetdecorstyle(&term.c.attr, 0); 1606 } else { 1607 fprintf(stderr, 1608 "erresc: unknown underline " 1609 "style %d\n", 1610 idx); 1611 } 1612 } 1613 break; 1614 case 5: /* slow blink */ 1615 /* FALLTHROUGH */ 1616 case 6: /* rapid blink */ 1617 term.c.attr.mode |= ATTR_BLINK; 1618 break; 1619 case 7: 1620 term.c.attr.mode |= ATTR_REVERSE; 1621 break; 1622 case 8: 1623 term.c.attr.mode |= ATTR_INVISIBLE; 1624 break; 1625 case 9: 1626 term.c.attr.mode |= ATTR_STRUCK; 1627 break; 1628 case 22: 1629 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1630 break; 1631 case 23: 1632 term.c.attr.mode &= ~ATTR_ITALIC; 1633 break; 1634 case 24: 1635 term.c.attr.mode &= ~ATTR_UNDERLINE; 1636 tsetdecorstyle(&term.c.attr, 0); 1637 break; 1638 case 25: 1639 term.c.attr.mode &= ~ATTR_BLINK; 1640 break; 1641 case 27: 1642 term.c.attr.mode &= ~ATTR_REVERSE; 1643 break; 1644 case 28: 1645 term.c.attr.mode &= ~ATTR_INVISIBLE; 1646 break; 1647 case 29: 1648 term.c.attr.mode &= ~ATTR_STRUCK; 1649 break; 1650 case 38: 1651 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1652 term.c.attr.fg = idx; 1653 break; 1654 case 39: 1655 term.c.attr.fg = defaultfg; 1656 break; 1657 case 48: 1658 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1659 term.c.attr.bg = idx; 1660 break; 1661 case 49: 1662 term.c.attr.bg = defaultbg; 1663 break; 1664 case 58: 1665 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1666 tsetdecorcolor(&term.c.attr, idx); 1667 break; 1668 case 59: 1669 tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR); 1670 break; 1671 default: 1672 if (BETWEEN(attr[i], 30, 37)) { 1673 term.c.attr.fg = attr[i] - 30; 1674 } else if (BETWEEN(attr[i], 40, 47)) { 1675 term.c.attr.bg = attr[i] - 40; 1676 } else if (BETWEEN(attr[i], 90, 97)) { 1677 term.c.attr.fg = attr[i] - 90 + 8; 1678 } else if (BETWEEN(attr[i], 100, 107)) { 1679 term.c.attr.bg = attr[i] - 100 + 8; 1680 } else { 1681 fprintf(stderr, 1682 "erresc(default): gfx attr %d unknown\n", 1683 attr[i]); 1684 csidump(); 1685 } 1686 break; 1687 } 1688 } 1689 } 1690 1691 void 1692 tsetscroll(int t, int b) 1693 { 1694 int temp; 1695 1696 LIMIT(t, 0, term.row-1); 1697 LIMIT(b, 0, term.row-1); 1698 if (t > b) { 1699 temp = t; 1700 t = b; 1701 b = temp; 1702 } 1703 term.top = t; 1704 term.bot = b; 1705 } 1706 1707 void 1708 tsetmode(int priv, int set, const int *args, int narg) 1709 { 1710 int alt; const int *lim; 1711 1712 for (lim = args + narg; args < lim; ++args) { 1713 if (priv) { 1714 switch (*args) { 1715 case 1: /* DECCKM -- Cursor key */ 1716 xsetmode(set, MODE_APPCURSOR); 1717 break; 1718 case 5: /* DECSCNM -- Reverse video */ 1719 xsetmode(set, MODE_REVERSE); 1720 break; 1721 case 6: /* DECOM -- Origin */ 1722 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1723 tmoveato(0, 0); 1724 break; 1725 case 7: /* DECAWM -- Auto wrap */ 1726 MODBIT(term.mode, set, MODE_WRAP); 1727 break; 1728 case 0: /* Error (IGNORED) */ 1729 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1730 case 3: /* DECCOLM -- Column (IGNORED) */ 1731 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1732 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1733 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1734 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1735 case 42: /* DECNRCM -- National characters (IGNORED) */ 1736 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1737 break; 1738 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1739 xsetmode(!set, MODE_HIDE); 1740 break; 1741 case 9: /* X10 mouse compatibility mode */ 1742 xsetpointermotion(0); 1743 xsetmode(0, MODE_MOUSE); 1744 xsetmode(set, MODE_MOUSEX10); 1745 break; 1746 case 1000: /* 1000: report button press */ 1747 xsetpointermotion(0); 1748 xsetmode(0, MODE_MOUSE); 1749 xsetmode(set, MODE_MOUSEBTN); 1750 break; 1751 case 1002: /* 1002: report motion on button press */ 1752 xsetpointermotion(0); 1753 xsetmode(0, MODE_MOUSE); 1754 xsetmode(set, MODE_MOUSEMOTION); 1755 break; 1756 case 1003: /* 1003: enable all mouse motions */ 1757 xsetpointermotion(set); 1758 xsetmode(0, MODE_MOUSE); 1759 xsetmode(set, MODE_MOUSEMANY); 1760 break; 1761 case 1004: /* 1004: send focus events to tty */ 1762 xsetmode(set, MODE_FOCUS); 1763 break; 1764 case 1006: /* 1006: extended reporting mode */ 1765 xsetmode(set, MODE_MOUSESGR); 1766 break; 1767 case 1034: 1768 xsetmode(set, MODE_8BIT); 1769 break; 1770 case 1049: /* swap screen & set/restore cursor as xterm */ 1771 if (!allowaltscreen) 1772 break; 1773 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1774 /* FALLTHROUGH */ 1775 case 47: /* swap screen */ 1776 case 1047: 1777 if (!allowaltscreen) 1778 break; 1779 alt = IS_SET(MODE_ALTSCREEN); 1780 if (alt) { 1781 tclearregion(0, 0, term.col-1, 1782 term.row-1); 1783 } 1784 if (set ^ alt) /* set is always 1 or 0 */ 1785 tswapscreen(); 1786 if (*args != 1049) 1787 break; 1788 /* FALLTHROUGH */ 1789 case 1048: 1790 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1791 break; 1792 case 2004: /* 2004: bracketed paste mode */ 1793 xsetmode(set, MODE_BRCKTPASTE); 1794 break; 1795 /* Not implemented mouse modes. See comments there. */ 1796 case 1001: /* mouse highlight mode; can hang the 1797 terminal by design when implemented. */ 1798 case 1005: /* UTF-8 mouse mode; will confuse 1799 applications not supporting UTF-8 1800 and luit. */ 1801 case 1015: /* urxvt mangled mouse mode; incompatible 1802 and can be mistaken for other control 1803 codes. */ 1804 break; 1805 default: 1806 fprintf(stderr, 1807 "erresc: unknown private set/reset mode %d\n", 1808 *args); 1809 break; 1810 } 1811 } else { 1812 switch (*args) { 1813 case 0: /* Error (IGNORED) */ 1814 break; 1815 case 2: 1816 xsetmode(set, MODE_KBDLOCK); 1817 break; 1818 case 4: /* IRM -- Insertion-replacement */ 1819 MODBIT(term.mode, set, MODE_INSERT); 1820 break; 1821 case 12: /* SRM -- Send/Receive */ 1822 MODBIT(term.mode, !set, MODE_ECHO); 1823 break; 1824 case 20: /* LNM -- Linefeed/new line */ 1825 MODBIT(term.mode, set, MODE_CRLF); 1826 break; 1827 default: 1828 fprintf(stderr, 1829 "erresc: unknown set/reset mode %d\n", 1830 *args); 1831 break; 1832 } 1833 } 1834 } 1835 } 1836 1837 void 1838 csihandle(void) 1839 { 1840 char buf[40]; 1841 int len; 1842 1843 switch (csiescseq.mode[0]) { 1844 default: 1845 unknown: 1846 fprintf(stderr, "erresc: unknown csi "); 1847 csidump(); 1848 /* die(""); */ 1849 break; 1850 case '@': /* ICH -- Insert <n> blank char */ 1851 DEFAULT(csiescseq.arg[0], 1); 1852 tinsertblank(csiescseq.arg[0]); 1853 break; 1854 case 'A': /* CUU -- Cursor <n> Up */ 1855 DEFAULT(csiescseq.arg[0], 1); 1856 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1857 break; 1858 case 'B': /* CUD -- Cursor <n> Down */ 1859 case 'e': /* VPR --Cursor <n> Down */ 1860 DEFAULT(csiescseq.arg[0], 1); 1861 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1862 break; 1863 case 'i': /* MC -- Media Copy */ 1864 switch (csiescseq.arg[0]) { 1865 case 0: 1866 tdump(); 1867 break; 1868 case 1: 1869 tdumpline(term.c.y); 1870 break; 1871 case 2: 1872 tdumpsel(); 1873 break; 1874 case 4: 1875 term.mode &= ~MODE_PRINT; 1876 break; 1877 case 5: 1878 term.mode |= MODE_PRINT; 1879 break; 1880 } 1881 break; 1882 case 'c': /* DA -- Device Attributes */ 1883 if (csiescseq.arg[0] == 0) 1884 ttywrite(vtiden, strlen(vtiden), 0); 1885 break; 1886 case 'b': /* REP -- if last char is printable print it <n> more times */ 1887 DEFAULT(csiescseq.arg[0], 1); 1888 if (term.lastc) 1889 while (csiescseq.arg[0]-- > 0) 1890 tputc(term.lastc); 1891 break; 1892 case 'C': /* CUF -- Cursor <n> Forward */ 1893 case 'a': /* HPR -- Cursor <n> Forward */ 1894 DEFAULT(csiescseq.arg[0], 1); 1895 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1896 break; 1897 case 'D': /* CUB -- Cursor <n> Backward */ 1898 DEFAULT(csiescseq.arg[0], 1); 1899 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1900 break; 1901 case 'E': /* CNL -- Cursor <n> Down and first col */ 1902 DEFAULT(csiescseq.arg[0], 1); 1903 tmoveto(0, term.c.y+csiescseq.arg[0]); 1904 break; 1905 case 'F': /* CPL -- Cursor <n> Up and first col */ 1906 DEFAULT(csiescseq.arg[0], 1); 1907 tmoveto(0, term.c.y-csiescseq.arg[0]); 1908 break; 1909 case 'g': /* TBC -- Tabulation clear */ 1910 switch (csiescseq.arg[0]) { 1911 case 0: /* clear current tab stop */ 1912 term.tabs[term.c.x] = 0; 1913 break; 1914 case 3: /* clear all the tabs */ 1915 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1916 break; 1917 default: 1918 goto unknown; 1919 } 1920 break; 1921 case 'G': /* CHA -- Move to <col> */ 1922 case '`': /* HPA */ 1923 DEFAULT(csiescseq.arg[0], 1); 1924 tmoveto(csiescseq.arg[0]-1, term.c.y); 1925 break; 1926 case 'H': /* CUP -- Move to <row> <col> */ 1927 case 'f': /* HVP */ 1928 DEFAULT(csiescseq.arg[0], 1); 1929 DEFAULT(csiescseq.arg[1], 1); 1930 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1931 break; 1932 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1933 DEFAULT(csiescseq.arg[0], 1); 1934 tputtab(csiescseq.arg[0]); 1935 break; 1936 case 'J': /* ED -- Clear screen */ 1937 switch (csiescseq.arg[0]) { 1938 case 0: /* below */ 1939 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1940 if (term.c.y < term.row-1) { 1941 tclearregion(0, term.c.y+1, term.col-1, 1942 term.row-1); 1943 } 1944 break; 1945 case 1: /* above */ 1946 if (term.c.y > 1) 1947 tclearregion(0, 0, term.col-1, term.c.y-1); 1948 tclearregion(0, term.c.y, term.c.x, term.c.y); 1949 break; 1950 case 2: /* all */ 1951 tclearregion(0, 0, term.col-1, term.row-1); 1952 break; 1953 default: 1954 goto unknown; 1955 } 1956 break; 1957 case 'K': /* EL -- Clear line */ 1958 switch (csiescseq.arg[0]) { 1959 case 0: /* right */ 1960 tclearregion(term.c.x, term.c.y, term.col-1, 1961 term.c.y); 1962 break; 1963 case 1: /* left */ 1964 tclearregion(0, term.c.y, term.c.x, term.c.y); 1965 break; 1966 case 2: /* all */ 1967 tclearregion(0, term.c.y, term.col-1, term.c.y); 1968 break; 1969 } 1970 break; 1971 case 'S': /* SU -- Scroll <n> line up */ 1972 DEFAULT(csiescseq.arg[0], 1); 1973 tscrollup(term.top, csiescseq.arg[0], 0); 1974 break; 1975 case 'T': /* SD -- Scroll <n> line down */ 1976 DEFAULT(csiescseq.arg[0], 1); 1977 tscrolldown(term.top, csiescseq.arg[0], 0); 1978 break; 1979 case 'L': /* IL -- Insert <n> blank lines */ 1980 DEFAULT(csiescseq.arg[0], 1); 1981 tinsertblankline(csiescseq.arg[0]); 1982 break; 1983 case 'l': /* RM -- Reset Mode */ 1984 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1985 break; 1986 case 'M': /* DL -- Delete <n> lines */ 1987 DEFAULT(csiescseq.arg[0], 1); 1988 tdeleteline(csiescseq.arg[0]); 1989 break; 1990 case 'X': /* ECH -- Erase <n> char */ 1991 DEFAULT(csiescseq.arg[0], 1); 1992 tclearregion(term.c.x, term.c.y, 1993 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1994 break; 1995 case 'P': /* DCH -- Delete <n> char */ 1996 DEFAULT(csiescseq.arg[0], 1); 1997 tdeletechar(csiescseq.arg[0]); 1998 break; 1999 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 2000 DEFAULT(csiescseq.arg[0], 1); 2001 tputtab(-csiescseq.arg[0]); 2002 break; 2003 case 'd': /* VPA -- Move to <row> */ 2004 DEFAULT(csiescseq.arg[0], 1); 2005 tmoveato(term.c.x, csiescseq.arg[0]-1); 2006 break; 2007 case 'h': /* SM -- Set terminal mode */ 2008 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 2009 break; 2010 case 'm': /* SGR -- Terminal attribute (color) */ 2011 tsetattr(csiescseq.arg, csiescseq.narg); 2012 break; 2013 case 'n': /* DSR -- Device Status Report */ 2014 switch (csiescseq.arg[0]) { 2015 case 5: /* Status Report "OK" `0n` */ 2016 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 2017 break; 2018 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ 2019 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 2020 term.c.y+1, term.c.x+1); 2021 ttywrite(buf, len, 0); 2022 break; 2023 default: 2024 goto unknown; 2025 } 2026 break; 2027 case 'r': /* DECSTBM -- Set Scrolling Region */ 2028 if (csiescseq.priv) { 2029 goto unknown; 2030 } else { 2031 DEFAULT(csiescseq.arg[0], 1); 2032 DEFAULT(csiescseq.arg[1], term.row); 2033 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 2034 tmoveato(0, 0); 2035 } 2036 break; 2037 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 2038 tcursor(CURSOR_SAVE); 2039 break; 2040 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 2041 tcursor(CURSOR_LOAD); 2042 break; 2043 case ' ': 2044 switch (csiescseq.mode[1]) { 2045 case 'q': /* DECSCUSR -- Set Cursor Style */ 2046 if (xsetcursor(csiescseq.arg[0])) 2047 goto unknown; 2048 break; 2049 default: 2050 goto unknown; 2051 } 2052 break; 2053 case '>': 2054 switch (csiescseq.mode[1]) { 2055 case 'q': /* XTVERSION -- Print terminal name and version */ 2056 len = snprintf(buf, sizeof(buf), 2057 "\033P>|st-graphics(%s)\033\\", VERSION); 2058 ttywrite(buf, len, 0); 2059 break; 2060 default: 2061 goto unknown; 2062 } 2063 break; 2064 case 't': /* XTWINOPS -- Window manipulation */ 2065 switch (csiescseq.arg[0]) { 2066 case 14: /* Report text area size in pixels. */ 2067 len = snprintf(buf, sizeof(buf), "\033[4;%i;%it", 2068 term.pixh, term.pixw); 2069 ttywrite(buf, len, 0); 2070 break; 2071 case 16: /* Report character cell size in pixels. */ 2072 len = snprintf(buf, sizeof(buf), "\033[6;%i;%it", 2073 term.pixh / term.row, 2074 term.pixw / term.col); 2075 ttywrite(buf, len, 0); 2076 break; 2077 case 18: /* Report the size of the text area in characters. */ 2078 len = snprintf(buf, sizeof(buf), "\033[8;%i;%it", 2079 term.row, term.col); 2080 ttywrite(buf, len, 0); 2081 break; 2082 default: 2083 goto unknown; 2084 } 2085 break; 2086 } 2087 } 2088 2089 void 2090 csidump(void) 2091 { 2092 size_t i; 2093 uint c; 2094 2095 fprintf(stderr, "ESC["); 2096 for (i = 0; i < csiescseq.len; i++) { 2097 c = csiescseq.buf[i] & 0xff; 2098 if (isprint(c)) { 2099 putc(c, stderr); 2100 } else if (c == '\n') { 2101 fprintf(stderr, "(\\n)"); 2102 } else if (c == '\r') { 2103 fprintf(stderr, "(\\r)"); 2104 } else if (c == 0x1b) { 2105 fprintf(stderr, "(\\e)"); 2106 } else { 2107 fprintf(stderr, "(%02x)", c); 2108 } 2109 } 2110 putc('\n', stderr); 2111 } 2112 2113 void 2114 csireset(void) 2115 { 2116 memset(&csiescseq, 0, sizeof(csiescseq)); 2117 } 2118 2119 void 2120 osc_color_response(int num, int index, int is_osc4) 2121 { 2122 int n; 2123 char buf[32]; 2124 unsigned char r, g, b; 2125 2126 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 2127 fprintf(stderr, "erresc: failed to fetch %s color %d\n", 2128 is_osc4 ? "osc4" : "osc", 2129 is_osc4 ? num : index); 2130 return; 2131 } 2132 2133 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 2134 is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 2135 if (n < 0 || n >= sizeof(buf)) { 2136 fprintf(stderr, "error: %s while printing %s response\n", 2137 n < 0 ? "snprintf failed" : "truncation occurred", 2138 is_osc4 ? "osc4" : "osc"); 2139 } else { 2140 ttywrite(buf, n, 1); 2141 } 2142 } 2143 2144 void 2145 strhandle(void) 2146 { 2147 char *p = NULL, *dec; 2148 int j, narg, par; 2149 const struct { int idx; char *str; } osc_table[] = { 2150 { defaultfg, "foreground" }, 2151 { defaultbg, "background" }, 2152 { defaultcs, "cursor" } 2153 }; 2154 2155 term.esc &= ~(ESC_STR_END|ESC_STR); 2156 strparse(); 2157 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 2158 2159 switch (strescseq.type) { 2160 case ']': /* OSC -- Operating System Command */ 2161 switch (par) { 2162 case 0: 2163 if (narg > 1) { 2164 xsettitle(strescseq.args[1]); 2165 xseticontitle(strescseq.args[1]); 2166 } 2167 return; 2168 case 1: 2169 if (narg > 1) 2170 xseticontitle(strescseq.args[1]); 2171 return; 2172 case 2: 2173 if (narg > 1) 2174 xsettitle(strescseq.args[1]); 2175 return; 2176 case 52: 2177 if (narg > 2 && allowwindowops) { 2178 dec = base64dec(strescseq.args[2]); 2179 if (dec) { 2180 xsetsel(dec); 2181 xclipcopy(); 2182 } else { 2183 fprintf(stderr, "erresc: invalid base64\n"); 2184 } 2185 } 2186 return; 2187 case 10: 2188 case 11: 2189 case 12: 2190 if (narg < 2) 2191 break; 2192 p = strescseq.args[1]; 2193 if ((j = par - 10) < 0 || j >= LEN(osc_table)) 2194 break; /* shouldn't be possible */ 2195 2196 if (!strcmp(p, "?")) { 2197 osc_color_response(par, osc_table[j].idx, 0); 2198 } else if (xsetcolorname(osc_table[j].idx, p)) { 2199 fprintf(stderr, "erresc: invalid %s color: %s\n", 2200 osc_table[j].str, p); 2201 } else { 2202 tfulldirt(); 2203 } 2204 return; 2205 case 4: /* color set */ 2206 if (narg < 3) 2207 break; 2208 p = strescseq.args[2]; 2209 /* FALLTHROUGH */ 2210 case 104: /* color reset */ 2211 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2212 2213 if (p && !strcmp(p, "?")) { 2214 osc_color_response(j, 0, 1); 2215 } else if (xsetcolorname(j, p)) { 2216 if (par == 104 && narg <= 1) { 2217 xloadcols(); 2218 return; /* color reset without parameter */ 2219 } 2220 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2221 j, p ? p : "(null)"); 2222 } else { 2223 /* 2224 * TODO if defaultbg color is changed, borders 2225 * are dirty 2226 */ 2227 tfulldirt(); 2228 } 2229 return; 2230 } 2231 break; 2232 case 'k': /* old title set compatibility */ 2233 xsettitle(strescseq.args[0]); 2234 return; 2235 case '_': /* APC -- Application Program Command */ 2236 if (gr_parse_command(strescseq.buf, strescseq.len)) { 2237 GraphicsCommandResult *res = &graphics_command_result; 2238 if (res->create_placeholder) { 2239 tcreateimgplaceholder( 2240 res->placeholder.image_id, 2241 res->placeholder.placement_id, 2242 res->placeholder.columns, 2243 res->placeholder.rows, 2244 res->placeholder.do_not_move_cursor); 2245 } 2246 if (res->response[0]) 2247 ttywrite(res->response, strlen(res->response), 2248 0); 2249 if (res->redraw) 2250 tfulldirt(); 2251 return; 2252 } 2253 return; 2254 case 'P': /* DCS -- Device Control String */ 2255 case '^': /* PM -- Privacy Message */ 2256 return; 2257 } 2258 2259 fprintf(stderr, "erresc: unknown str "); 2260 strdump(); 2261 } 2262 2263 void 2264 strparse(void) 2265 { 2266 int c; 2267 char *p = strescseq.buf; 2268 2269 strescseq.narg = 0; 2270 strescseq.buf[strescseq.len] = '\0'; 2271 2272 if (*p == '\0') 2273 return; 2274 2275 while (strescseq.narg < STR_ARG_SIZ) { 2276 strescseq.args[strescseq.narg++] = p; 2277 while ((c = *p) != ';' && c != '\0') 2278 ++p; 2279 if (c == '\0') 2280 return; 2281 *p++ = '\0'; 2282 } 2283 } 2284 2285 void 2286 strdump(void) 2287 { 2288 size_t i; 2289 uint c; 2290 2291 fprintf(stderr, "ESC%c", strescseq.type); 2292 for (i = 0; i < strescseq.len; i++) { 2293 c = strescseq.buf[i] & 0xff; 2294 if (c == '\0') { 2295 putc('\n', stderr); 2296 return; 2297 } else if (isprint(c)) { 2298 putc(c, stderr); 2299 } else if (c == '\n') { 2300 fprintf(stderr, "(\\n)"); 2301 } else if (c == '\r') { 2302 fprintf(stderr, "(\\r)"); 2303 } else if (c == 0x1b) { 2304 fprintf(stderr, "(\\e)"); 2305 } else { 2306 fprintf(stderr, "(%02x)", c); 2307 } 2308 } 2309 fprintf(stderr, "ESC\\\n"); 2310 } 2311 2312 void 2313 strreset(void) 2314 { 2315 strescseq = (STREscape){ 2316 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2317 .siz = STR_BUF_SIZ, 2318 }; 2319 } 2320 2321 void 2322 sendbreak(const Arg *arg) 2323 { 2324 if (tcsendbreak(cmdfd, 0)) 2325 perror("Error sending break"); 2326 } 2327 2328 void 2329 tprinter(char *s, size_t len) 2330 { 2331 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2332 perror("Error writing to output file"); 2333 close(iofd); 2334 iofd = -1; 2335 } 2336 } 2337 2338 void 2339 toggleprinter(const Arg *arg) 2340 { 2341 term.mode ^= MODE_PRINT; 2342 } 2343 2344 void 2345 printscreen(const Arg *arg) 2346 { 2347 tdump(); 2348 } 2349 2350 void 2351 printsel(const Arg *arg) 2352 { 2353 tdumpsel(); 2354 } 2355 2356 void 2357 tdumpsel(void) 2358 { 2359 char *ptr; 2360 2361 if ((ptr = getsel())) { 2362 tprinter(ptr, strlen(ptr)); 2363 free(ptr); 2364 } 2365 } 2366 2367 void 2368 tdumpline(int n) 2369 { 2370 char buf[UTF_SIZ]; 2371 const Glyph *bp, *end; 2372 2373 bp = &term.line[n][0]; 2374 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2375 if (bp != end || bp->u != ' ') { 2376 for ( ; bp <= end; ++bp) 2377 tprinter(buf, utf8encode(bp->u, buf)); 2378 } 2379 tprinter("\n", 1); 2380 } 2381 2382 void 2383 tdump(void) 2384 { 2385 int i; 2386 2387 for (i = 0; i < term.row; ++i) 2388 tdumpline(i); 2389 } 2390 2391 void 2392 tputtab(int n) 2393 { 2394 uint x = term.c.x; 2395 2396 if (n > 0) { 2397 while (x < term.col && n--) 2398 for (++x; x < term.col && !term.tabs[x]; ++x) 2399 /* nothing */ ; 2400 } else if (n < 0) { 2401 while (x > 0 && n++) 2402 for (--x; x > 0 && !term.tabs[x]; --x) 2403 /* nothing */ ; 2404 } 2405 term.c.x = LIMIT(x, 0, term.col-1); 2406 } 2407 2408 void 2409 tdefutf8(char ascii) 2410 { 2411 if (ascii == 'G') 2412 term.mode |= MODE_UTF8; 2413 else if (ascii == '@') 2414 term.mode &= ~MODE_UTF8; 2415 } 2416 2417 void 2418 tdeftran(char ascii) 2419 { 2420 static char cs[] = "0B"; 2421 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2422 char *p; 2423 2424 if ((p = strchr(cs, ascii)) == NULL) { 2425 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2426 } else { 2427 term.trantbl[term.icharset] = vcs[p - cs]; 2428 } 2429 } 2430 2431 void 2432 tdectest(char c) 2433 { 2434 int x, y; 2435 2436 if (c == '8') { /* DEC screen alignment test. */ 2437 for (x = 0; x < term.col; ++x) { 2438 for (y = 0; y < term.row; ++y) 2439 tsetchar('E', &term.c.attr, x, y); 2440 } 2441 } 2442 } 2443 2444 void 2445 tstrsequence(uchar c) 2446 { 2447 switch (c) { 2448 case 0x90: /* DCS -- Device Control String */ 2449 c = 'P'; 2450 break; 2451 case 0x9f: /* APC -- Application Program Command */ 2452 c = '_'; 2453 break; 2454 case 0x9e: /* PM -- Privacy Message */ 2455 c = '^'; 2456 break; 2457 case 0x9d: /* OSC -- Operating System Command */ 2458 c = ']'; 2459 break; 2460 } 2461 strreset(); 2462 strescseq.type = c; 2463 term.esc |= ESC_STR; 2464 } 2465 2466 void 2467 tcontrolcode(uchar ascii) 2468 { 2469 switch (ascii) { 2470 case '\t': /* HT */ 2471 tputtab(1); 2472 return; 2473 case '\b': /* BS */ 2474 tmoveto(term.c.x-1, term.c.y); 2475 return; 2476 case '\r': /* CR */ 2477 tmoveto(0, term.c.y); 2478 return; 2479 case '\f': /* LF */ 2480 case '\v': /* VT */ 2481 case '\n': /* LF */ 2482 /* go to first col if the mode is set */ 2483 tnewline(IS_SET(MODE_CRLF)); 2484 return; 2485 case '\a': /* BEL */ 2486 if (term.esc & ESC_STR_END) { 2487 /* backwards compatibility to xterm */ 2488 strhandle(); 2489 } else { 2490 xbell(); 2491 } 2492 break; 2493 case '\033': /* ESC */ 2494 csireset(); 2495 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2496 term.esc |= ESC_START; 2497 return; 2498 case '\016': /* SO (LS1 -- Locking shift 1) */ 2499 case '\017': /* SI (LS0 -- Locking shift 0) */ 2500 term.charset = 1 - (ascii - '\016'); 2501 return; 2502 case '\032': /* SUB */ 2503 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2504 /* FALLTHROUGH */ 2505 case '\030': /* CAN */ 2506 csireset(); 2507 break; 2508 case '\005': /* ENQ (IGNORED) */ 2509 case '\000': /* NUL (IGNORED) */ 2510 case '\021': /* XON (IGNORED) */ 2511 case '\023': /* XOFF (IGNORED) */ 2512 case 0177: /* DEL (IGNORED) */ 2513 return; 2514 case 0x80: /* TODO: PAD */ 2515 case 0x81: /* TODO: HOP */ 2516 case 0x82: /* TODO: BPH */ 2517 case 0x83: /* TODO: NBH */ 2518 case 0x84: /* TODO: IND */ 2519 break; 2520 case 0x85: /* NEL -- Next line */ 2521 tnewline(1); /* always go to first col */ 2522 break; 2523 case 0x86: /* TODO: SSA */ 2524 case 0x87: /* TODO: ESA */ 2525 break; 2526 case 0x88: /* HTS -- Horizontal tab stop */ 2527 term.tabs[term.c.x] = 1; 2528 break; 2529 case 0x89: /* TODO: HTJ */ 2530 case 0x8a: /* TODO: VTS */ 2531 case 0x8b: /* TODO: PLD */ 2532 case 0x8c: /* TODO: PLU */ 2533 case 0x8d: /* TODO: RI */ 2534 case 0x8e: /* TODO: SS2 */ 2535 case 0x8f: /* TODO: SS3 */ 2536 case 0x91: /* TODO: PU1 */ 2537 case 0x92: /* TODO: PU2 */ 2538 case 0x93: /* TODO: STS */ 2539 case 0x94: /* TODO: CCH */ 2540 case 0x95: /* TODO: MW */ 2541 case 0x96: /* TODO: SPA */ 2542 case 0x97: /* TODO: EPA */ 2543 case 0x98: /* TODO: SOS */ 2544 case 0x99: /* TODO: SGCI */ 2545 break; 2546 case 0x9a: /* DECID -- Identify Terminal */ 2547 ttywrite(vtiden, strlen(vtiden), 0); 2548 break; 2549 case 0x9b: /* TODO: CSI */ 2550 case 0x9c: /* TODO: ST */ 2551 break; 2552 case 0x90: /* DCS -- Device Control String */ 2553 case 0x9d: /* OSC -- Operating System Command */ 2554 case 0x9e: /* PM -- Privacy Message */ 2555 case 0x9f: /* APC -- Application Program Command */ 2556 tstrsequence(ascii); 2557 return; 2558 } 2559 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2560 term.esc &= ~(ESC_STR_END|ESC_STR); 2561 } 2562 2563 /* 2564 * returns 1 when the sequence is finished and it hasn't to read 2565 * more characters for this sequence, otherwise 0 2566 */ 2567 int 2568 eschandle(uchar ascii) 2569 { 2570 switch (ascii) { 2571 case '[': 2572 term.esc |= ESC_CSI; 2573 return 0; 2574 case '#': 2575 term.esc |= ESC_TEST; 2576 return 0; 2577 case '%': 2578 term.esc |= ESC_UTF8; 2579 return 0; 2580 case 'P': /* DCS -- Device Control String */ 2581 case '_': /* APC -- Application Program Command */ 2582 case '^': /* PM -- Privacy Message */ 2583 case ']': /* OSC -- Operating System Command */ 2584 case 'k': /* old title set compatibility */ 2585 tstrsequence(ascii); 2586 return 0; 2587 case 'n': /* LS2 -- Locking shift 2 */ 2588 case 'o': /* LS3 -- Locking shift 3 */ 2589 term.charset = 2 + (ascii - 'n'); 2590 break; 2591 case '(': /* GZD4 -- set primary charset G0 */ 2592 case ')': /* G1D4 -- set secondary charset G1 */ 2593 case '*': /* G2D4 -- set tertiary charset G2 */ 2594 case '+': /* G3D4 -- set quaternary charset G3 */ 2595 term.icharset = ascii - '('; 2596 term.esc |= ESC_ALTCHARSET; 2597 return 0; 2598 case 'D': /* IND -- Linefeed */ 2599 if (term.c.y == term.bot) { 2600 tscrollup(term.top, 1, 1); 2601 } else { 2602 tmoveto(term.c.x, term.c.y+1); 2603 } 2604 break; 2605 case 'E': /* NEL -- Next line */ 2606 tnewline(1); /* always go to first col */ 2607 break; 2608 case 'H': /* HTS -- Horizontal tab stop */ 2609 term.tabs[term.c.x] = 1; 2610 break; 2611 case 'M': /* RI -- Reverse index */ 2612 if (term.c.y == term.top) { 2613 tscrolldown(term.top, 1, 1); 2614 } else { 2615 tmoveto(term.c.x, term.c.y-1); 2616 } 2617 break; 2618 case 'Z': /* DECID -- Identify Terminal */ 2619 ttywrite(vtiden, strlen(vtiden), 0); 2620 break; 2621 case 'c': /* RIS -- Reset to initial state */ 2622 treset(); 2623 resettitle(); 2624 xloadcols(); 2625 break; 2626 case '=': /* DECPAM -- Application keypad */ 2627 xsetmode(1, MODE_APPKEYPAD); 2628 break; 2629 case '>': /* DECPNM -- Normal keypad */ 2630 xsetmode(0, MODE_APPKEYPAD); 2631 break; 2632 case '7': /* DECSC -- Save Cursor */ 2633 tcursor(CURSOR_SAVE); 2634 break; 2635 case '8': /* DECRC -- Restore Cursor */ 2636 tcursor(CURSOR_LOAD); 2637 break; 2638 case '\\': /* ST -- String Terminator */ 2639 if (term.esc & ESC_STR_END) 2640 strhandle(); 2641 break; 2642 default: 2643 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2644 (uchar) ascii, isprint(ascii)? ascii:'.'); 2645 break; 2646 } 2647 return 1; 2648 } 2649 2650 void 2651 tputc(Rune u) 2652 { 2653 char c[UTF_SIZ]; 2654 int control; 2655 int width, len; 2656 Glyph *gp; 2657 2658 control = ISCONTROL(u); 2659 if (u < 127 || !IS_SET(MODE_UTF8)) { 2660 c[0] = u; 2661 width = len = 1; 2662 } else { 2663 len = utf8encode(u, c); 2664 if (!control && (width = wcwidth(u)) == -1) 2665 width = 1; 2666 } 2667 2668 if (IS_SET(MODE_PRINT)) 2669 tprinter(c, len); 2670 2671 /* 2672 * STR sequence must be checked before anything else 2673 * because it uses all following characters until it 2674 * receives a ESC, a SUB, a ST or any other C1 control 2675 * character. 2676 */ 2677 if (term.esc & ESC_STR) { 2678 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2679 ISCONTROLC1(u)) { 2680 term.esc &= ~(ESC_START|ESC_STR); 2681 term.esc |= ESC_STR_END; 2682 goto check_control_code; 2683 } 2684 2685 if (strescseq.len+len >= strescseq.siz) { 2686 /* 2687 * Here is a bug in terminals. If the user never sends 2688 * some code to stop the str or esc command, then st 2689 * will stop responding. But this is better than 2690 * silently failing with unknown characters. At least 2691 * then users will report back. 2692 * 2693 * In the case users ever get fixed, here is the code: 2694 */ 2695 /* 2696 * term.esc = 0; 2697 * strhandle(); 2698 */ 2699 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2700 return; 2701 strescseq.siz *= 2; 2702 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2703 } 2704 2705 memmove(&strescseq.buf[strescseq.len], c, len); 2706 strescseq.len += len; 2707 return; 2708 } 2709 2710 check_control_code: 2711 /* 2712 * Actions of control codes must be performed as soon they arrive 2713 * because they can be embedded inside a control sequence, and 2714 * they must not cause conflicts with sequences. 2715 */ 2716 if (control) { 2717 /* in UTF-8 mode ignore handling C1 control characters */ 2718 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2719 return; 2720 tcontrolcode(u); 2721 /* 2722 * control codes are not shown ever 2723 */ 2724 if (!term.esc) 2725 term.lastc = 0; 2726 return; 2727 } else if (term.esc & ESC_START) { 2728 if (term.esc & ESC_CSI) { 2729 csiescseq.buf[csiescseq.len++] = u; 2730 if (BETWEEN(u, 0x40, 0x7E) 2731 || csiescseq.len >= \ 2732 sizeof(csiescseq.buf)-1) { 2733 term.esc = 0; 2734 csiparse(); 2735 csihandle(); 2736 } 2737 return; 2738 } else if (term.esc & ESC_UTF8) { 2739 tdefutf8(u); 2740 } else if (term.esc & ESC_ALTCHARSET) { 2741 tdeftran(u); 2742 } else if (term.esc & ESC_TEST) { 2743 tdectest(u); 2744 } else { 2745 if (!eschandle(u)) 2746 return; 2747 /* sequence already finished */ 2748 } 2749 term.esc = 0; 2750 /* 2751 * All characters which form part of a sequence are not 2752 * printed 2753 */ 2754 return; 2755 } 2756 if (selected(term.c.x, term.c.y)) 2757 selclear(); 2758 2759 if (width == 0) { 2760 // It's probably a combining char. Combining characters are not 2761 // supported, so we just ignore them, unless it denotes the row and 2762 // column of an image character. 2763 if (term.c.y <= 0 && term.c.x <= 0) 2764 return; 2765 else if (term.c.x == 0) 2766 gp = &term.line[term.c.y-1][term.col-1]; 2767 else if (term.c.state & CURSOR_WRAPNEXT) 2768 gp = &term.line[term.c.y][term.c.x]; 2769 else 2770 gp = &term.line[term.c.y][term.c.x-1]; 2771 uint16_t num = diacritic_to_num(u); 2772 if (num && (gp->mode & ATTR_IMAGE)) { 2773 unsigned diaccount = tgetimgdiacriticcount(gp); 2774 if (diaccount == 0) 2775 tsetimgrow(gp, num); 2776 else if (diaccount == 1) 2777 tsetimgcol(gp, num); 2778 else if (diaccount == 2) 2779 tsetimg4thbyteplus1(gp, num); 2780 tsetimgdiacriticcount(gp, diaccount + 1); 2781 } 2782 term.lastc = u; 2783 return; 2784 } 2785 2786 gp = &term.line[term.c.y][term.c.x]; 2787 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2788 gp->mode |= ATTR_WRAP; 2789 tnewline(1); 2790 gp = &term.line[term.c.y][term.c.x]; 2791 } 2792 2793 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2794 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2795 gp->mode &= ~ATTR_WIDE; 2796 } 2797 2798 if (term.c.x+width > term.col) { 2799 tnewline(1); 2800 gp = &term.line[term.c.y][term.c.x]; 2801 } 2802 2803 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2804 term.lastc = u; 2805 2806 if (width == 2) { 2807 gp->mode |= ATTR_WIDE; 2808 if (term.c.x+1 < term.col) { 2809 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2810 gp[2].u = ' '; 2811 gp[2].mode &= ~ATTR_WDUMMY; 2812 } 2813 gp[1].u = '\0'; 2814 gp[1].mode = ATTR_WDUMMY; 2815 } 2816 } 2817 if (term.c.x+width < term.col) { 2818 tmoveto(term.c.x+width, term.c.y); 2819 } else { 2820 term.c.state |= CURSOR_WRAPNEXT; 2821 } 2822 } 2823 2824 int 2825 twrite(const char *buf, int buflen, int show_ctrl) 2826 { 2827 int charsize; 2828 Rune u; 2829 int n; 2830 2831 for (n = 0; n < buflen; n += charsize) { 2832 if (IS_SET(MODE_UTF8)) { 2833 /* process a complete utf8 char */ 2834 charsize = utf8decode(buf + n, &u, buflen - n); 2835 if (charsize == 0) 2836 break; 2837 } else { 2838 u = buf[n] & 0xFF; 2839 charsize = 1; 2840 } 2841 if (show_ctrl && ISCONTROL(u)) { 2842 if (u & 0x80) { 2843 u &= 0x7f; 2844 tputc('^'); 2845 tputc('['); 2846 } else if (u != '\n' && u != '\r' && u != '\t') { 2847 u ^= 0x40; 2848 tputc('^'); 2849 } 2850 } 2851 tputc(u); 2852 } 2853 return n; 2854 } 2855 2856 void 2857 tresize(int col, int row) 2858 { 2859 int i, j; 2860 int minrow = MIN(row, term.row); 2861 int mincol = MIN(col, term.col); 2862 int *bp; 2863 TCursor c; 2864 2865 if (col < 1 || row < 1) { 2866 fprintf(stderr, 2867 "tresize: error resizing to %dx%d\n", col, row); 2868 return; 2869 } 2870 2871 /* 2872 * slide screen to keep cursor where we expect it - 2873 * tscrollup would work here, but we can optimize to 2874 * memmove because we're freeing the earlier lines 2875 */ 2876 for (i = 0; i <= term.c.y - row; i++) { 2877 free(term.line[i]); 2878 free(term.alt[i]); 2879 } 2880 /* ensure that both src and dst are not NULL */ 2881 if (i > 0) { 2882 memmove(term.line, term.line + i, row * sizeof(Line)); 2883 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2884 } 2885 for (i += row; i < term.row; i++) { 2886 free(term.line[i]); 2887 free(term.alt[i]); 2888 } 2889 2890 /* resize to new height */ 2891 term.line = xrealloc(term.line, row * sizeof(Line)); 2892 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2893 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2894 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2895 2896 for (i = 0; i < HISTSIZE; i++) { 2897 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2898 for (j = mincol; j < col; j++) { 2899 term.hist[i][j] = term.c.attr; 2900 term.hist[i][j].u = ' '; 2901 } 2902 } 2903 2904 /* resize each row to new width, zero-pad if needed */ 2905 for (i = 0; i < minrow; i++) { 2906 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2907 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2908 } 2909 2910 /* allocate any new rows */ 2911 for (/* i = minrow */; i < row; i++) { 2912 term.line[i] = xmalloc(col * sizeof(Glyph)); 2913 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2914 } 2915 if (col > term.col) { 2916 bp = term.tabs + term.col; 2917 2918 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2919 while (--bp > term.tabs && !*bp) 2920 /* nothing */ ; 2921 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2922 *bp = 1; 2923 } 2924 /* update terminal size */ 2925 term.col = col; 2926 term.row = row; 2927 /* reset scrolling region */ 2928 tsetscroll(0, row-1); 2929 /* make use of the LIMIT in tmoveto */ 2930 tmoveto(term.c.x, term.c.y); 2931 /* Clearing both screens (it makes dirty all lines) */ 2932 c = term.c; 2933 for (i = 0; i < 2; i++) { 2934 if (mincol < col && 0 < minrow) { 2935 tclearregion(mincol, 0, col - 1, minrow - 1); 2936 } 2937 if (0 < col && minrow < row) { 2938 tclearregion(0, minrow, col - 1, row - 1); 2939 } 2940 tswapscreen(); 2941 tcursor(CURSOR_LOAD); 2942 } 2943 term.c = c; 2944 } 2945 2946 void 2947 resettitle(void) 2948 { 2949 xsettitle(NULL); 2950 } 2951 2952 void 2953 drawregion(int x1, int y1, int x2, int y2) 2954 { 2955 int y; 2956 2957 xstartimagedraw(term.dirty, term.row); 2958 2959 for (y = y1; y < y2; y++) { 2960 if (!term.dirty[y]) 2961 continue; 2962 2963 term.dirty[y] = 0; 2964 xdrawline(TLINE(y), x1, y, x2); 2965 } 2966 2967 xfinishimagedraw(); 2968 } 2969 2970 void 2971 draw(void) 2972 { 2973 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2974 2975 if (!xstartdraw()) 2976 return; 2977 2978 /* adjust cursor position */ 2979 LIMIT(term.ocx, 0, term.col-1); 2980 LIMIT(term.ocy, 0, term.row-1); 2981 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2982 term.ocx--; 2983 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2984 cx--; 2985 2986 drawregion(0, 0, term.col, term.row); 2987 if (term.scr == 0) 2988 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2989 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2990 term.ocx = cx; 2991 term.ocy = term.c.y; 2992 xfinishdraw(); 2993 if (ocx != term.ocx || ocy != term.ocy) 2994 xximspot(term.ocx, term.ocy); 2995 } 2996 2997 void 2998 redraw(void) 2999 { 3000 tfulldirt(); 3001 draw(); 3002 } 3003 3004 Glyph 3005 getglyphat(int col, int row) 3006 { 3007 return term.line[row][col]; 3008 }