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