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