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