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