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