st.c (59204B)
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 1285 void 1286 tclearregion(int x1, int y1, int x2, int y2) 1287 { 1288 int x, y, temp; 1289 Glyph *gp; 1290 1291 if (x1 > x2) 1292 temp = x1, x1 = x2, x2 = temp; 1293 if (y1 > y2) 1294 temp = y1, y1 = y2, y2 = temp; 1295 1296 LIMIT(x1, 0, term.col-1); 1297 LIMIT(x2, 0, term.col-1); 1298 LIMIT(y1, 0, term.row-1); 1299 LIMIT(y2, 0, term.row-1); 1300 1301 for (y = y1; y <= y2; y++) { 1302 term.dirty[y] = 1; 1303 for (x = x1; x <= x2; x++) { 1304 gp = &term.line[y][x]; 1305 if (selected(x, y)) 1306 selclear(); 1307 gp->fg = term.c.attr.fg; 1308 gp->bg = term.c.attr.bg; 1309 gp->mode = 0; 1310 gp->u = ' '; 1311 } 1312 } 1313 } 1314 1315 void 1316 tdeletechar(int n) 1317 { 1318 int dst, src, size; 1319 Glyph *line; 1320 1321 LIMIT(n, 0, term.col - term.c.x); 1322 1323 dst = term.c.x; 1324 src = term.c.x + n; 1325 size = term.col - src; 1326 line = term.line[term.c.y]; 1327 1328 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1329 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1330 } 1331 1332 void 1333 tinsertblank(int n) 1334 { 1335 int dst, src, size; 1336 Glyph *line; 1337 1338 LIMIT(n, 0, term.col - term.c.x); 1339 1340 dst = term.c.x + n; 1341 src = term.c.x; 1342 size = term.col - dst; 1343 line = term.line[term.c.y]; 1344 1345 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1346 tclearregion(src, term.c.y, dst - 1, term.c.y); 1347 } 1348 1349 void 1350 tinsertblankline(int n) 1351 { 1352 if (BETWEEN(term.c.y, term.top, term.bot)) 1353 tscrolldown(term.c.y, n, 0); 1354 } 1355 1356 void 1357 tdeleteline(int n) 1358 { 1359 if (BETWEEN(term.c.y, term.top, term.bot)) 1360 tscrollup(term.c.y, n, 0); 1361 } 1362 1363 int32_t 1364 tdefcolor(const int *attr, int *npar, int l) 1365 { 1366 int32_t idx = -1; 1367 uint r, g, b; 1368 1369 switch (attr[*npar + 1]) { 1370 case 2: /* direct color in RGB space */ 1371 if (*npar + 4 >= l) { 1372 fprintf(stderr, 1373 "erresc(38): Incorrect number of parameters (%d)\n", 1374 *npar); 1375 break; 1376 } 1377 r = attr[*npar + 2]; 1378 g = attr[*npar + 3]; 1379 b = attr[*npar + 4]; 1380 *npar += 4; 1381 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1382 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1383 r, g, b); 1384 else 1385 idx = TRUECOLOR(r, g, b); 1386 break; 1387 case 5: /* indexed color */ 1388 if (*npar + 2 >= l) { 1389 fprintf(stderr, 1390 "erresc(38): Incorrect number of parameters (%d)\n", 1391 *npar); 1392 break; 1393 } 1394 *npar += 2; 1395 if (!BETWEEN(attr[*npar], 0, 255)) 1396 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1397 else 1398 idx = attr[*npar]; 1399 break; 1400 case 0: /* implemented defined (only foreground) */ 1401 case 1: /* transparent */ 1402 case 3: /* direct color in CMY space */ 1403 case 4: /* direct color in CMYK space */ 1404 default: 1405 fprintf(stderr, 1406 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1407 break; 1408 } 1409 1410 return idx; 1411 } 1412 1413 void 1414 tsetattr(const int *attr, int l) 1415 { 1416 int i; 1417 int32_t idx; 1418 1419 for (i = 0; i < l; i++) { 1420 switch (attr[i]) { 1421 case 0: 1422 term.c.attr.mode &= ~( 1423 ATTR_BOLD | 1424 ATTR_FAINT | 1425 ATTR_ITALIC | 1426 ATTR_UNDERLINE | 1427 ATTR_BLINK | 1428 ATTR_REVERSE | 1429 ATTR_INVISIBLE | 1430 ATTR_STRUCK ); 1431 term.c.attr.fg = defaultfg; 1432 term.c.attr.bg = defaultbg; 1433 break; 1434 case 1: 1435 term.c.attr.mode |= ATTR_BOLD; 1436 break; 1437 case 2: 1438 term.c.attr.mode |= ATTR_FAINT; 1439 break; 1440 case 3: 1441 term.c.attr.mode |= ATTR_ITALIC; 1442 break; 1443 case 4: 1444 term.c.attr.mode |= ATTR_UNDERLINE; 1445 break; 1446 case 5: /* slow blink */ 1447 /* FALLTHROUGH */ 1448 case 6: /* rapid blink */ 1449 term.c.attr.mode |= ATTR_BLINK; 1450 break; 1451 case 7: 1452 term.c.attr.mode |= ATTR_REVERSE; 1453 break; 1454 case 8: 1455 term.c.attr.mode |= ATTR_INVISIBLE; 1456 break; 1457 case 9: 1458 term.c.attr.mode |= ATTR_STRUCK; 1459 break; 1460 case 22: 1461 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1462 break; 1463 case 23: 1464 term.c.attr.mode &= ~ATTR_ITALIC; 1465 break; 1466 case 24: 1467 term.c.attr.mode &= ~ATTR_UNDERLINE; 1468 break; 1469 case 25: 1470 term.c.attr.mode &= ~ATTR_BLINK; 1471 break; 1472 case 27: 1473 term.c.attr.mode &= ~ATTR_REVERSE; 1474 break; 1475 case 28: 1476 term.c.attr.mode &= ~ATTR_INVISIBLE; 1477 break; 1478 case 29: 1479 term.c.attr.mode &= ~ATTR_STRUCK; 1480 break; 1481 case 38: 1482 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1483 term.c.attr.fg = idx; 1484 break; 1485 case 39: 1486 term.c.attr.fg = defaultfg; 1487 break; 1488 case 48: 1489 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1490 term.c.attr.bg = idx; 1491 break; 1492 case 49: 1493 term.c.attr.bg = defaultbg; 1494 break; 1495 default: 1496 if (BETWEEN(attr[i], 30, 37)) { 1497 term.c.attr.fg = attr[i] - 30; 1498 } else if (BETWEEN(attr[i], 40, 47)) { 1499 term.c.attr.bg = attr[i] - 40; 1500 } else if (BETWEEN(attr[i], 90, 97)) { 1501 term.c.attr.fg = attr[i] - 90 + 8; 1502 } else if (BETWEEN(attr[i], 100, 107)) { 1503 term.c.attr.bg = attr[i] - 100 + 8; 1504 } else { 1505 fprintf(stderr, 1506 "erresc(default): gfx attr %d unknown\n", 1507 attr[i]); 1508 csidump(); 1509 } 1510 break; 1511 } 1512 } 1513 } 1514 1515 void 1516 tsetscroll(int t, int b) 1517 { 1518 int temp; 1519 1520 LIMIT(t, 0, term.row-1); 1521 LIMIT(b, 0, term.row-1); 1522 if (t > b) { 1523 temp = t; 1524 t = b; 1525 b = temp; 1526 } 1527 term.top = t; 1528 term.bot = b; 1529 } 1530 1531 void 1532 tsetmode(int priv, int set, const int *args, int narg) 1533 { 1534 int alt; const int *lim; 1535 1536 for (lim = args + narg; args < lim; ++args) { 1537 if (priv) { 1538 switch (*args) { 1539 case 1: /* DECCKM -- Cursor key */ 1540 xsetmode(set, MODE_APPCURSOR); 1541 break; 1542 case 5: /* DECSCNM -- Reverse video */ 1543 xsetmode(set, MODE_REVERSE); 1544 break; 1545 case 6: /* DECOM -- Origin */ 1546 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1547 tmoveato(0, 0); 1548 break; 1549 case 7: /* DECAWM -- Auto wrap */ 1550 MODBIT(term.mode, set, MODE_WRAP); 1551 break; 1552 case 0: /* Error (IGNORED) */ 1553 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1554 case 3: /* DECCOLM -- Column (IGNORED) */ 1555 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1556 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1557 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1558 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1559 case 42: /* DECNRCM -- National characters (IGNORED) */ 1560 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1561 break; 1562 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1563 xsetmode(!set, MODE_HIDE); 1564 break; 1565 case 9: /* X10 mouse compatibility mode */ 1566 xsetpointermotion(0); 1567 xsetmode(0, MODE_MOUSE); 1568 xsetmode(set, MODE_MOUSEX10); 1569 break; 1570 case 1000: /* 1000: report button press */ 1571 xsetpointermotion(0); 1572 xsetmode(0, MODE_MOUSE); 1573 xsetmode(set, MODE_MOUSEBTN); 1574 break; 1575 case 1002: /* 1002: report motion on button press */ 1576 xsetpointermotion(0); 1577 xsetmode(0, MODE_MOUSE); 1578 xsetmode(set, MODE_MOUSEMOTION); 1579 break; 1580 case 1003: /* 1003: enable all mouse motions */ 1581 xsetpointermotion(set); 1582 xsetmode(0, MODE_MOUSE); 1583 xsetmode(set, MODE_MOUSEMANY); 1584 break; 1585 case 1004: /* 1004: send focus events to tty */ 1586 xsetmode(set, MODE_FOCUS); 1587 break; 1588 case 1006: /* 1006: extended reporting mode */ 1589 xsetmode(set, MODE_MOUSESGR); 1590 break; 1591 case 1034: 1592 xsetmode(set, MODE_8BIT); 1593 break; 1594 case 1049: /* swap screen & set/restore cursor as xterm */ 1595 if (!allowaltscreen) 1596 break; 1597 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1598 /* FALLTHROUGH */ 1599 case 47: /* swap screen */ 1600 case 1047: 1601 if (!allowaltscreen) 1602 break; 1603 alt = IS_SET(MODE_ALTSCREEN); 1604 if (alt) { 1605 tclearregion(0, 0, term.col-1, 1606 term.row-1); 1607 } 1608 if (set ^ alt) /* set is always 1 or 0 */ 1609 tswapscreen(); 1610 if (*args != 1049) 1611 break; 1612 /* FALLTHROUGH */ 1613 case 1048: 1614 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1615 break; 1616 case 2004: /* 2004: bracketed paste mode */ 1617 xsetmode(set, MODE_BRCKTPASTE); 1618 break; 1619 /* Not implemented mouse modes. See comments there. */ 1620 case 1001: /* mouse highlight mode; can hang the 1621 terminal by design when implemented. */ 1622 case 1005: /* UTF-8 mouse mode; will confuse 1623 applications not supporting UTF-8 1624 and luit. */ 1625 case 1015: /* urxvt mangled mouse mode; incompatible 1626 and can be mistaken for other control 1627 codes. */ 1628 break; 1629 default: 1630 fprintf(stderr, 1631 "erresc: unknown private set/reset mode %d\n", 1632 *args); 1633 break; 1634 } 1635 } else { 1636 switch (*args) { 1637 case 0: /* Error (IGNORED) */ 1638 break; 1639 case 2: 1640 xsetmode(set, MODE_KBDLOCK); 1641 break; 1642 case 4: /* IRM -- Insertion-replacement */ 1643 MODBIT(term.mode, set, MODE_INSERT); 1644 break; 1645 case 12: /* SRM -- Send/Receive */ 1646 MODBIT(term.mode, !set, MODE_ECHO); 1647 break; 1648 case 20: /* LNM -- Linefeed/new line */ 1649 MODBIT(term.mode, set, MODE_CRLF); 1650 break; 1651 default: 1652 fprintf(stderr, 1653 "erresc: unknown set/reset mode %d\n", 1654 *args); 1655 break; 1656 } 1657 } 1658 } 1659 } 1660 1661 void 1662 csihandle(void) 1663 { 1664 char buf[40]; 1665 int len; 1666 1667 switch (csiescseq.mode[0]) { 1668 default: 1669 unknown: 1670 fprintf(stderr, "erresc: unknown csi "); 1671 csidump(); 1672 /* die(""); */ 1673 break; 1674 case '@': /* ICH -- Insert <n> blank char */ 1675 DEFAULT(csiescseq.arg[0], 1); 1676 tinsertblank(csiescseq.arg[0]); 1677 break; 1678 case 'A': /* CUU -- Cursor <n> Up */ 1679 DEFAULT(csiescseq.arg[0], 1); 1680 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1681 break; 1682 case 'B': /* CUD -- Cursor <n> Down */ 1683 case 'e': /* VPR --Cursor <n> Down */ 1684 DEFAULT(csiescseq.arg[0], 1); 1685 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1686 break; 1687 case 'i': /* MC -- Media Copy */ 1688 switch (csiescseq.arg[0]) { 1689 case 0: 1690 tdump(); 1691 break; 1692 case 1: 1693 tdumpline(term.c.y); 1694 break; 1695 case 2: 1696 tdumpsel(); 1697 break; 1698 case 4: 1699 term.mode &= ~MODE_PRINT; 1700 break; 1701 case 5: 1702 term.mode |= MODE_PRINT; 1703 break; 1704 } 1705 break; 1706 case 'c': /* DA -- Device Attributes */ 1707 if (csiescseq.arg[0] == 0) 1708 ttywrite(vtiden, strlen(vtiden), 0); 1709 break; 1710 case 'b': /* REP -- if last char is printable print it <n> more times */ 1711 LIMIT(csiescseq.arg[0], 1, 65535); 1712 if (term.lastc) 1713 while (csiescseq.arg[0]-- > 0) 1714 tputc(term.lastc); 1715 break; 1716 case 'C': /* CUF -- Cursor <n> Forward */ 1717 case 'a': /* HPR -- Cursor <n> Forward */ 1718 DEFAULT(csiescseq.arg[0], 1); 1719 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1720 break; 1721 case 'D': /* CUB -- Cursor <n> Backward */ 1722 DEFAULT(csiescseq.arg[0], 1); 1723 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1724 break; 1725 case 'E': /* CNL -- Cursor <n> Down and first col */ 1726 DEFAULT(csiescseq.arg[0], 1); 1727 tmoveto(0, term.c.y+csiescseq.arg[0]); 1728 break; 1729 case 'F': /* CPL -- Cursor <n> Up and first col */ 1730 DEFAULT(csiescseq.arg[0], 1); 1731 tmoveto(0, term.c.y-csiescseq.arg[0]); 1732 break; 1733 case 'g': /* TBC -- Tabulation clear */ 1734 switch (csiescseq.arg[0]) { 1735 case 0: /* clear current tab stop */ 1736 term.tabs[term.c.x] = 0; 1737 break; 1738 case 3: /* clear all the tabs */ 1739 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1740 break; 1741 default: 1742 goto unknown; 1743 } 1744 break; 1745 case 'G': /* CHA -- Move to <col> */ 1746 case '`': /* HPA */ 1747 DEFAULT(csiescseq.arg[0], 1); 1748 tmoveto(csiescseq.arg[0]-1, term.c.y); 1749 break; 1750 case 'H': /* CUP -- Move to <row> <col> */ 1751 case 'f': /* HVP */ 1752 DEFAULT(csiescseq.arg[0], 1); 1753 DEFAULT(csiescseq.arg[1], 1); 1754 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1755 break; 1756 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1757 DEFAULT(csiescseq.arg[0], 1); 1758 tputtab(csiescseq.arg[0]); 1759 break; 1760 case 'J': /* ED -- Clear screen */ 1761 switch (csiescseq.arg[0]) { 1762 case 0: /* below */ 1763 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1764 if (term.c.y < term.row-1) { 1765 tclearregion(0, term.c.y+1, term.col-1, 1766 term.row-1); 1767 } 1768 break; 1769 case 1: /* above */ 1770 if (term.c.y > 0) 1771 tclearregion(0, 0, term.col-1, term.c.y-1); 1772 tclearregion(0, term.c.y, term.c.x, term.c.y); 1773 break; 1774 case 2: /* all */ 1775 tclearregion(0, 0, term.col-1, term.row-1); 1776 break; 1777 default: 1778 goto unknown; 1779 } 1780 break; 1781 case 'K': /* EL -- Clear line */ 1782 switch (csiescseq.arg[0]) { 1783 case 0: /* right */ 1784 tclearregion(term.c.x, term.c.y, term.col-1, 1785 term.c.y); 1786 break; 1787 case 1: /* left */ 1788 tclearregion(0, term.c.y, term.c.x, term.c.y); 1789 break; 1790 case 2: /* all */ 1791 tclearregion(0, term.c.y, term.col-1, term.c.y); 1792 break; 1793 } 1794 break; 1795 case 'S': /* SU -- Scroll <n> line up */ 1796 if (csiescseq.priv) break; 1797 DEFAULT(csiescseq.arg[0], 1); 1798 tscrollup(term.top, csiescseq.arg[0], 0); 1799 break; 1800 case 'T': /* SD -- Scroll <n> line down */ 1801 DEFAULT(csiescseq.arg[0], 1); 1802 tscrolldown(term.top, csiescseq.arg[0], 0); 1803 break; 1804 case 'L': /* IL -- Insert <n> blank lines */ 1805 DEFAULT(csiescseq.arg[0], 1); 1806 tinsertblankline(csiescseq.arg[0]); 1807 break; 1808 case 'l': /* RM -- Reset Mode */ 1809 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1810 break; 1811 case 'M': /* DL -- Delete <n> lines */ 1812 DEFAULT(csiescseq.arg[0], 1); 1813 tdeleteline(csiescseq.arg[0]); 1814 break; 1815 case 'X': /* ECH -- Erase <n> char */ 1816 DEFAULT(csiescseq.arg[0], 1); 1817 tclearregion(term.c.x, term.c.y, 1818 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1819 break; 1820 case 'P': /* DCH -- Delete <n> char */ 1821 DEFAULT(csiescseq.arg[0], 1); 1822 tdeletechar(csiescseq.arg[0]); 1823 break; 1824 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 1825 DEFAULT(csiescseq.arg[0], 1); 1826 tputtab(-csiescseq.arg[0]); 1827 break; 1828 case 'd': /* VPA -- Move to <row> */ 1829 DEFAULT(csiescseq.arg[0], 1); 1830 tmoveato(term.c.x, csiescseq.arg[0]-1); 1831 break; 1832 case 'h': /* SM -- Set terminal mode */ 1833 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1834 break; 1835 case 'm': /* SGR -- Terminal attribute (color) */ 1836 tsetattr(csiescseq.arg, csiescseq.narg); 1837 break; 1838 case 'n': /* DSR -- Device Status Report */ 1839 switch (csiescseq.arg[0]) { 1840 case 5: /* Status Report "OK" `0n` */ 1841 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 1842 break; 1843 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ 1844 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1845 term.c.y+1, term.c.x+1); 1846 ttywrite(buf, len, 0); 1847 break; 1848 default: 1849 goto unknown; 1850 } 1851 break; 1852 case 'r': /* DECSTBM -- Set Scrolling Region */ 1853 if (csiescseq.priv) { 1854 goto unknown; 1855 } else { 1856 DEFAULT(csiescseq.arg[0], 1); 1857 DEFAULT(csiescseq.arg[1], term.row); 1858 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1859 tmoveato(0, 0); 1860 } 1861 break; 1862 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1863 tcursor(CURSOR_SAVE); 1864 break; 1865 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1866 tcursor(CURSOR_LOAD); 1867 break; 1868 case ' ': 1869 switch (csiescseq.mode[1]) { 1870 case 'q': /* DECSCUSR -- Set Cursor Style */ 1871 if (xsetcursor(csiescseq.arg[0])) 1872 goto unknown; 1873 break; 1874 default: 1875 goto unknown; 1876 } 1877 break; 1878 } 1879 } 1880 1881 void 1882 csidump(void) 1883 { 1884 size_t i; 1885 uint c; 1886 1887 fprintf(stderr, "ESC["); 1888 for (i = 0; i < csiescseq.len; i++) { 1889 c = csiescseq.buf[i] & 0xff; 1890 if (isprint(c)) { 1891 putc(c, stderr); 1892 } else if (c == '\n') { 1893 fprintf(stderr, "(\\n)"); 1894 } else if (c == '\r') { 1895 fprintf(stderr, "(\\r)"); 1896 } else if (c == 0x1b) { 1897 fprintf(stderr, "(\\e)"); 1898 } else { 1899 fprintf(stderr, "(%02x)", c); 1900 } 1901 } 1902 putc('\n', stderr); 1903 } 1904 1905 void 1906 csireset(void) 1907 { 1908 memset(&csiescseq, 0, sizeof(csiescseq)); 1909 } 1910 1911 void 1912 osc_color_response(int num, int index, int is_osc4) 1913 { 1914 int n; 1915 char buf[32]; 1916 unsigned char r, g, b; 1917 1918 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 1919 fprintf(stderr, "erresc: failed to fetch %s color %d\n", 1920 is_osc4 ? "osc4" : "osc", 1921 is_osc4 ? num : index); 1922 return; 1923 } 1924 1925 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1926 is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 1927 if (n < 0 || n >= sizeof(buf)) { 1928 fprintf(stderr, "error: %s while printing %s response\n", 1929 n < 0 ? "snprintf failed" : "truncation occurred", 1930 is_osc4 ? "osc4" : "osc"); 1931 } else { 1932 ttywrite(buf, n, 1); 1933 } 1934 } 1935 1936 void 1937 strhandle(void) 1938 { 1939 char *p = NULL, *dec; 1940 int j, narg, par; 1941 const struct { int idx; char *str; } osc_table[] = { 1942 { defaultfg, "foreground" }, 1943 { defaultbg, "background" }, 1944 { defaultcs, "cursor" } 1945 }; 1946 1947 term.esc &= ~(ESC_STR_END|ESC_STR); 1948 strparse(); 1949 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1950 1951 switch (strescseq.type) { 1952 case ']': /* OSC -- Operating System Command */ 1953 switch (par) { 1954 case 0: 1955 if (narg > 1) { 1956 xsettitle(strescseq.args[1]); 1957 xseticontitle(strescseq.args[1]); 1958 } 1959 return; 1960 case 1: 1961 if (narg > 1) 1962 xseticontitle(strescseq.args[1]); 1963 return; 1964 case 2: 1965 if (narg > 1) 1966 xsettitle(strescseq.args[1]); 1967 return; 1968 case 52: 1969 if (narg > 2 && allowwindowops) { 1970 dec = base64dec(strescseq.args[2]); 1971 if (dec) { 1972 xsetsel(dec); 1973 xclipcopy(); 1974 } else { 1975 fprintf(stderr, "erresc: invalid base64\n"); 1976 } 1977 } 1978 return; 1979 case 10: 1980 case 11: 1981 case 12: 1982 if (narg < 2) 1983 break; 1984 p = strescseq.args[1]; 1985 if ((j = par - 10) < 0 || j >= LEN(osc_table)) 1986 break; /* shouldn't be possible */ 1987 1988 if (!strcmp(p, "?")) { 1989 osc_color_response(par, osc_table[j].idx, 0); 1990 } else if (xsetcolorname(osc_table[j].idx, p)) { 1991 fprintf(stderr, "erresc: invalid %s color: %s\n", 1992 osc_table[j].str, p); 1993 } else { 1994 tfulldirt(); 1995 } 1996 return; 1997 case 4: /* color set */ 1998 if (narg < 3) 1999 break; 2000 p = strescseq.args[2]; 2001 /* FALLTHROUGH */ 2002 case 104: /* color reset */ 2003 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2004 2005 if (p && !strcmp(p, "?")) { 2006 osc_color_response(j, 0, 1); 2007 } else if (xsetcolorname(j, p)) { 2008 if (par == 104 && narg <= 1) { 2009 xloadcols(); 2010 return; /* color reset without parameter */ 2011 } 2012 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2013 j, p ? p : "(null)"); 2014 } else { 2015 /* 2016 * TODO if defaultbg color is changed, borders 2017 * are dirty 2018 */ 2019 tfulldirt(); 2020 } 2021 return; 2022 } 2023 break; 2024 case 'k': /* old title set compatibility */ 2025 xsettitle(strescseq.args[0]); 2026 return; 2027 case 'P': /* DCS -- Device Control String */ 2028 case '_': /* APC -- Application Program Command */ 2029 case '^': /* PM -- Privacy Message */ 2030 return; 2031 } 2032 2033 fprintf(stderr, "erresc: unknown str "); 2034 strdump(); 2035 } 2036 2037 void 2038 strparse(void) 2039 { 2040 int c; 2041 char *p = strescseq.buf; 2042 2043 strescseq.narg = 0; 2044 strescseq.buf[strescseq.len] = '\0'; 2045 2046 if (*p == '\0') 2047 return; 2048 2049 while (strescseq.narg < STR_ARG_SIZ) { 2050 strescseq.args[strescseq.narg++] = p; 2051 while ((c = *p) != ';' && c != '\0') 2052 ++p; 2053 if (c == '\0') 2054 return; 2055 *p++ = '\0'; 2056 } 2057 } 2058 2059 void 2060 strdump(void) 2061 { 2062 size_t i; 2063 uint c; 2064 2065 fprintf(stderr, "ESC%c", strescseq.type); 2066 for (i = 0; i < strescseq.len; i++) { 2067 c = strescseq.buf[i] & 0xff; 2068 if (c == '\0') { 2069 putc('\n', stderr); 2070 return; 2071 } else if (isprint(c)) { 2072 putc(c, stderr); 2073 } else if (c == '\n') { 2074 fprintf(stderr, "(\\n)"); 2075 } else if (c == '\r') { 2076 fprintf(stderr, "(\\r)"); 2077 } else if (c == 0x1b) { 2078 fprintf(stderr, "(\\e)"); 2079 } else { 2080 fprintf(stderr, "(%02x)", c); 2081 } 2082 } 2083 fprintf(stderr, "ESC\\\n"); 2084 } 2085 2086 void 2087 strreset(void) 2088 { 2089 strescseq = (STREscape){ 2090 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2091 .siz = STR_BUF_SIZ, 2092 }; 2093 } 2094 2095 void 2096 sendbreak(const Arg *arg) 2097 { 2098 if (tcsendbreak(cmdfd, 0)) 2099 perror("Error sending break"); 2100 } 2101 2102 void 2103 tprinter(char *s, size_t len) 2104 { 2105 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2106 perror("Error writing to output file"); 2107 close(iofd); 2108 iofd = -1; 2109 } 2110 } 2111 2112 void 2113 toggleprinter(const Arg *arg) 2114 { 2115 term.mode ^= MODE_PRINT; 2116 } 2117 2118 void 2119 printscreen(const Arg *arg) 2120 { 2121 tdump(); 2122 } 2123 2124 void 2125 printsel(const Arg *arg) 2126 { 2127 tdumpsel(); 2128 } 2129 2130 void 2131 tdumpsel(void) 2132 { 2133 char *ptr; 2134 2135 if ((ptr = getsel())) { 2136 tprinter(ptr, strlen(ptr)); 2137 free(ptr); 2138 } 2139 } 2140 2141 void 2142 tdumpline(int n) 2143 { 2144 char buf[UTF_SIZ]; 2145 const Glyph *bp, *end; 2146 2147 bp = &term.line[n][0]; 2148 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2149 if (bp != end || bp->u != ' ') { 2150 for ( ; bp <= end; ++bp) 2151 tprinter(buf, utf8encode(bp->u, buf)); 2152 } 2153 tprinter("\n", 1); 2154 } 2155 2156 void 2157 tdump(void) 2158 { 2159 int i; 2160 2161 for (i = 0; i < term.row; ++i) 2162 tdumpline(i); 2163 } 2164 2165 void 2166 tputtab(int n) 2167 { 2168 uint x = term.c.x; 2169 2170 if (n > 0) { 2171 while (x < term.col && n--) 2172 for (++x; x < term.col && !term.tabs[x]; ++x) 2173 /* nothing */ ; 2174 } else if (n < 0) { 2175 while (x > 0 && n++) 2176 for (--x; x > 0 && !term.tabs[x]; --x) 2177 /* nothing */ ; 2178 } 2179 term.c.x = LIMIT(x, 0, term.col-1); 2180 } 2181 2182 void 2183 tdefutf8(char ascii) 2184 { 2185 if (ascii == 'G') 2186 term.mode |= MODE_UTF8; 2187 else if (ascii == '@') 2188 term.mode &= ~MODE_UTF8; 2189 } 2190 2191 void 2192 tdeftran(char ascii) 2193 { 2194 static char cs[] = "0B"; 2195 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2196 char *p; 2197 2198 if ((p = strchr(cs, ascii)) == NULL) { 2199 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2200 } else { 2201 term.trantbl[term.icharset] = vcs[p - cs]; 2202 } 2203 } 2204 2205 void 2206 tdectest(char c) 2207 { 2208 int x, y; 2209 2210 if (c == '8') { /* DEC screen alignment test. */ 2211 for (x = 0; x < term.col; ++x) { 2212 for (y = 0; y < term.row; ++y) 2213 tsetchar('E', &term.c.attr, x, y); 2214 } 2215 } 2216 } 2217 2218 void 2219 tstrsequence(uchar c) 2220 { 2221 switch (c) { 2222 case 0x90: /* DCS -- Device Control String */ 2223 c = 'P'; 2224 break; 2225 case 0x9f: /* APC -- Application Program Command */ 2226 c = '_'; 2227 break; 2228 case 0x9e: /* PM -- Privacy Message */ 2229 c = '^'; 2230 break; 2231 case 0x9d: /* OSC -- Operating System Command */ 2232 c = ']'; 2233 break; 2234 } 2235 strreset(); 2236 strescseq.type = c; 2237 term.esc |= ESC_STR; 2238 } 2239 2240 void 2241 tcontrolcode(uchar ascii) 2242 { 2243 switch (ascii) { 2244 case '\t': /* HT */ 2245 tputtab(1); 2246 return; 2247 case '\b': /* BS */ 2248 tmoveto(term.c.x-1, term.c.y); 2249 return; 2250 case '\r': /* CR */ 2251 tmoveto(0, term.c.y); 2252 return; 2253 case '\f': /* LF */ 2254 case '\v': /* VT */ 2255 case '\n': /* LF */ 2256 /* go to first col if the mode is set */ 2257 tnewline(IS_SET(MODE_CRLF)); 2258 return; 2259 case '\a': /* BEL */ 2260 if (term.esc & ESC_STR_END) { 2261 /* backwards compatibility to xterm */ 2262 strhandle(); 2263 } else { 2264 xbell(); 2265 } 2266 break; 2267 case '\033': /* ESC */ 2268 csireset(); 2269 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2270 term.esc |= ESC_START; 2271 return; 2272 case '\016': /* SO (LS1 -- Locking shift 1) */ 2273 case '\017': /* SI (LS0 -- Locking shift 0) */ 2274 term.charset = 1 - (ascii - '\016'); 2275 return; 2276 case '\032': /* SUB */ 2277 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2278 /* FALLTHROUGH */ 2279 case '\030': /* CAN */ 2280 csireset(); 2281 break; 2282 case '\005': /* ENQ (IGNORED) */ 2283 case '\000': /* NUL (IGNORED) */ 2284 case '\021': /* XON (IGNORED) */ 2285 case '\023': /* XOFF (IGNORED) */ 2286 case 0177: /* DEL (IGNORED) */ 2287 return; 2288 case 0x80: /* TODO: PAD */ 2289 case 0x81: /* TODO: HOP */ 2290 case 0x82: /* TODO: BPH */ 2291 case 0x83: /* TODO: NBH */ 2292 case 0x84: /* TODO: IND */ 2293 break; 2294 case 0x85: /* NEL -- Next line */ 2295 tnewline(1); /* always go to first col */ 2296 break; 2297 case 0x86: /* TODO: SSA */ 2298 case 0x87: /* TODO: ESA */ 2299 break; 2300 case 0x88: /* HTS -- Horizontal tab stop */ 2301 term.tabs[term.c.x] = 1; 2302 break; 2303 case 0x89: /* TODO: HTJ */ 2304 case 0x8a: /* TODO: VTS */ 2305 case 0x8b: /* TODO: PLD */ 2306 case 0x8c: /* TODO: PLU */ 2307 case 0x8d: /* TODO: RI */ 2308 case 0x8e: /* TODO: SS2 */ 2309 case 0x8f: /* TODO: SS3 */ 2310 case 0x91: /* TODO: PU1 */ 2311 case 0x92: /* TODO: PU2 */ 2312 case 0x93: /* TODO: STS */ 2313 case 0x94: /* TODO: CCH */ 2314 case 0x95: /* TODO: MW */ 2315 case 0x96: /* TODO: SPA */ 2316 case 0x97: /* TODO: EPA */ 2317 case 0x98: /* TODO: SOS */ 2318 case 0x99: /* TODO: SGCI */ 2319 break; 2320 case 0x9a: /* DECID -- Identify Terminal */ 2321 ttywrite(vtiden, strlen(vtiden), 0); 2322 break; 2323 case 0x9b: /* TODO: CSI */ 2324 case 0x9c: /* TODO: ST */ 2325 break; 2326 case 0x90: /* DCS -- Device Control String */ 2327 case 0x9d: /* OSC -- Operating System Command */ 2328 case 0x9e: /* PM -- Privacy Message */ 2329 case 0x9f: /* APC -- Application Program Command */ 2330 tstrsequence(ascii); 2331 return; 2332 } 2333 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2334 term.esc &= ~(ESC_STR_END|ESC_STR); 2335 } 2336 2337 /* 2338 * returns 1 when the sequence is finished and it hasn't to read 2339 * more characters for this sequence, otherwise 0 2340 */ 2341 int 2342 eschandle(uchar ascii) 2343 { 2344 switch (ascii) { 2345 case '[': 2346 term.esc |= ESC_CSI; 2347 return 0; 2348 case '#': 2349 term.esc |= ESC_TEST; 2350 return 0; 2351 case '%': 2352 term.esc |= ESC_UTF8; 2353 return 0; 2354 case 'P': /* DCS -- Device Control String */ 2355 case '_': /* APC -- Application Program Command */ 2356 case '^': /* PM -- Privacy Message */ 2357 case ']': /* OSC -- Operating System Command */ 2358 case 'k': /* old title set compatibility */ 2359 tstrsequence(ascii); 2360 return 0; 2361 case 'n': /* LS2 -- Locking shift 2 */ 2362 case 'o': /* LS3 -- Locking shift 3 */ 2363 term.charset = 2 + (ascii - 'n'); 2364 break; 2365 case '(': /* GZD4 -- set primary charset G0 */ 2366 case ')': /* G1D4 -- set secondary charset G1 */ 2367 case '*': /* G2D4 -- set tertiary charset G2 */ 2368 case '+': /* G3D4 -- set quaternary charset G3 */ 2369 term.icharset = ascii - '('; 2370 term.esc |= ESC_ALTCHARSET; 2371 return 0; 2372 case 'D': /* IND -- Linefeed */ 2373 if (term.c.y == term.bot) { 2374 tscrollup(term.top, 1, 1); 2375 } else { 2376 tmoveto(term.c.x, term.c.y+1); 2377 } 2378 break; 2379 case 'E': /* NEL -- Next line */ 2380 tnewline(1); /* always go to first col */ 2381 break; 2382 case 'H': /* HTS -- Horizontal tab stop */ 2383 term.tabs[term.c.x] = 1; 2384 break; 2385 case 'M': /* RI -- Reverse index */ 2386 if (term.c.y == term.top) { 2387 tscrolldown(term.top, 1, 1); 2388 } else { 2389 tmoveto(term.c.x, term.c.y-1); 2390 } 2391 break; 2392 case 'Z': /* DECID -- Identify Terminal */ 2393 ttywrite(vtiden, strlen(vtiden), 0); 2394 break; 2395 case 'c': /* RIS -- Reset to initial state */ 2396 treset(); 2397 resettitle(); 2398 xloadcols(); 2399 xsetmode(0, MODE_HIDE); 2400 break; 2401 case '=': /* DECPAM -- Application keypad */ 2402 xsetmode(1, MODE_APPKEYPAD); 2403 break; 2404 case '>': /* DECPNM -- Normal keypad */ 2405 xsetmode(0, MODE_APPKEYPAD); 2406 break; 2407 case '7': /* DECSC -- Save Cursor */ 2408 tcursor(CURSOR_SAVE); 2409 break; 2410 case '8': /* DECRC -- Restore Cursor */ 2411 tcursor(CURSOR_LOAD); 2412 break; 2413 case '\\': /* ST -- String Terminator */ 2414 if (term.esc & ESC_STR_END) 2415 strhandle(); 2416 break; 2417 default: 2418 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2419 (uchar) ascii, isprint(ascii)? ascii:'.'); 2420 break; 2421 } 2422 return 1; 2423 } 2424 2425 void 2426 tputc(Rune u) 2427 { 2428 char c[UTF_SIZ]; 2429 int control; 2430 int width, len; 2431 Glyph *gp; 2432 2433 control = ISCONTROL(u); 2434 if (u < 127 || !IS_SET(MODE_UTF8)) { 2435 c[0] = u; 2436 width = len = 1; 2437 } else { 2438 len = utf8encode(u, c); 2439 if (!control && (width = wcwidth(u)) == -1) 2440 width = 1; 2441 } 2442 2443 if (IS_SET(MODE_PRINT)) 2444 tprinter(c, len); 2445 2446 /* 2447 * STR sequence must be checked before anything else 2448 * because it uses all following characters until it 2449 * receives a ESC, a SUB, a ST or any other C1 control 2450 * character. 2451 */ 2452 if (term.esc & ESC_STR) { 2453 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2454 ISCONTROLC1(u)) { 2455 term.esc &= ~(ESC_START|ESC_STR); 2456 term.esc |= ESC_STR_END; 2457 goto check_control_code; 2458 } 2459 2460 if (strescseq.len+len >= strescseq.siz) { 2461 /* 2462 * Here is a bug in terminals. If the user never sends 2463 * some code to stop the str or esc command, then st 2464 * will stop responding. But this is better than 2465 * silently failing with unknown characters. At least 2466 * then users will report back. 2467 * 2468 * In the case users ever get fixed, here is the code: 2469 */ 2470 /* 2471 * term.esc = 0; 2472 * strhandle(); 2473 */ 2474 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2475 return; 2476 strescseq.siz *= 2; 2477 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2478 } 2479 2480 memmove(&strescseq.buf[strescseq.len], c, len); 2481 strescseq.len += len; 2482 return; 2483 } 2484 2485 check_control_code: 2486 /* 2487 * Actions of control codes must be performed as soon they arrive 2488 * because they can be embedded inside a control sequence, and 2489 * they must not cause conflicts with sequences. 2490 */ 2491 if (control) { 2492 /* in UTF-8 mode ignore handling C1 control characters */ 2493 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2494 return; 2495 tcontrolcode(u); 2496 /* 2497 * control codes are not shown ever 2498 */ 2499 if (!term.esc) 2500 term.lastc = 0; 2501 return; 2502 } else if (term.esc & ESC_START) { 2503 if (term.esc & ESC_CSI) { 2504 csiescseq.buf[csiescseq.len++] = u; 2505 if (BETWEEN(u, 0x40, 0x7E) 2506 || csiescseq.len >= \ 2507 sizeof(csiescseq.buf)-1) { 2508 term.esc = 0; 2509 csiparse(); 2510 csihandle(); 2511 } 2512 return; 2513 } else if (term.esc & ESC_UTF8) { 2514 tdefutf8(u); 2515 } else if (term.esc & ESC_ALTCHARSET) { 2516 tdeftran(u); 2517 } else if (term.esc & ESC_TEST) { 2518 tdectest(u); 2519 } else { 2520 if (!eschandle(u)) 2521 return; 2522 /* sequence already finished */ 2523 } 2524 term.esc = 0; 2525 /* 2526 * All characters which form part of a sequence are not 2527 * printed 2528 */ 2529 return; 2530 } 2531 if (selected(term.c.x, term.c.y)) 2532 selclear(); 2533 2534 gp = &term.line[term.c.y][term.c.x]; 2535 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2536 gp->mode |= ATTR_WRAP; 2537 tnewline(1); 2538 gp = &term.line[term.c.y][term.c.x]; 2539 } 2540 2541 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2542 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2543 gp->mode &= ~ATTR_WIDE; 2544 } 2545 2546 if (term.c.x+width > term.col) { 2547 if (IS_SET(MODE_WRAP)) 2548 tnewline(1); 2549 else 2550 tmoveto(term.col - width, term.c.y); 2551 gp = &term.line[term.c.y][term.c.x]; 2552 } 2553 2554 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2555 term.lastc = u; 2556 2557 if (width == 2) { 2558 gp->mode |= ATTR_WIDE; 2559 if (term.c.x+1 < term.col) { 2560 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2561 gp[2].u = ' '; 2562 gp[2].mode &= ~ATTR_WDUMMY; 2563 } 2564 gp[1].u = '\0'; 2565 gp[1].mode = ATTR_WDUMMY; 2566 } 2567 } 2568 if (term.c.x+width < term.col) { 2569 tmoveto(term.c.x+width, term.c.y); 2570 } else { 2571 term.c.state |= CURSOR_WRAPNEXT; 2572 } 2573 } 2574 2575 int 2576 twrite(const char *buf, int buflen, int show_ctrl) 2577 { 2578 int charsize; 2579 Rune u; 2580 int n; 2581 2582 for (n = 0; n < buflen; n += charsize) { 2583 if (IS_SET(MODE_UTF8)) { 2584 /* process a complete utf8 char */ 2585 charsize = utf8decode(buf + n, &u, buflen - n); 2586 if (charsize == 0) 2587 break; 2588 } else { 2589 u = buf[n] & 0xFF; 2590 charsize = 1; 2591 } 2592 if (show_ctrl && ISCONTROL(u)) { 2593 if (u & 0x80) { 2594 u &= 0x7f; 2595 tputc('^'); 2596 tputc('['); 2597 } else if (u != '\n' && u != '\r' && u != '\t') { 2598 u ^= 0x40; 2599 tputc('^'); 2600 } 2601 } 2602 tputc(u); 2603 } 2604 return n; 2605 } 2606 2607 void 2608 tresize(int col, int row) 2609 { 2610 int i, j; 2611 int minrow = MIN(row, term.row); 2612 int mincol = MIN(col, term.col); 2613 int *bp; 2614 TCursor c; 2615 2616 if (col < 1 || row < 1) { 2617 fprintf(stderr, 2618 "tresize: error resizing to %dx%d\n", col, row); 2619 return; 2620 } 2621 2622 /* 2623 * slide screen to keep cursor where we expect it - 2624 * tscrollup would work here, but we can optimize to 2625 * memmove because we're freeing the earlier lines 2626 */ 2627 for (i = 0; i <= term.c.y - row; i++) { 2628 free(term.line[i]); 2629 free(term.alt[i]); 2630 } 2631 /* ensure that both src and dst are not NULL */ 2632 if (i > 0) { 2633 memmove(term.line, term.line + i, row * sizeof(Line)); 2634 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2635 } 2636 for (i += row; i < term.row; i++) { 2637 free(term.line[i]); 2638 free(term.alt[i]); 2639 } 2640 2641 /* resize to new height */ 2642 term.line = xrealloc(term.line, row * sizeof(Line)); 2643 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2644 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2645 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2646 2647 for (i = 0; i < HISTSIZE; i++) { 2648 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2649 for (j = mincol; j < col; j++) { 2650 term.hist[i][j] = term.c.attr; 2651 term.hist[i][j].u = ' '; 2652 } 2653 } 2654 2655 /* resize each row to new width, zero-pad if needed */ 2656 for (i = 0; i < minrow; i++) { 2657 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2658 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2659 } 2660 2661 /* allocate any new rows */ 2662 for (/* i = minrow */; i < row; i++) { 2663 term.line[i] = xmalloc(col * sizeof(Glyph)); 2664 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2665 } 2666 if (col > term.col) { 2667 bp = term.tabs + term.col; 2668 2669 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2670 while (--bp > term.tabs && !*bp) 2671 /* nothing */ ; 2672 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2673 *bp = 1; 2674 } 2675 /* update terminal size */ 2676 term.col = col; 2677 term.row = row; 2678 /* reset scrolling region */ 2679 tsetscroll(0, row-1); 2680 /* make use of the LIMIT in tmoveto */ 2681 tmoveto(term.c.x, term.c.y); 2682 /* Clearing both screens (it makes dirty all lines) */ 2683 c = term.c; 2684 for (i = 0; i < 2; i++) { 2685 if (mincol < col && 0 < minrow) { 2686 tclearregion(mincol, 0, col - 1, minrow - 1); 2687 } 2688 if (0 < col && minrow < row) { 2689 tclearregion(0, minrow, col - 1, row - 1); 2690 } 2691 tswapscreen(); 2692 tcursor(CURSOR_LOAD); 2693 } 2694 term.c = c; 2695 } 2696 2697 void 2698 resettitle(void) 2699 { 2700 xsettitle(NULL); 2701 } 2702 2703 void 2704 drawregion(int x1, int y1, int x2, int y2) 2705 { 2706 int y; 2707 2708 for (y = y1; y < y2; y++) { 2709 if (!term.dirty[y]) 2710 continue; 2711 2712 term.dirty[y] = 0; 2713 xdrawline(TLINE(y), x1, y, x2); 2714 } 2715 } 2716 2717 void 2718 draw(void) 2719 { 2720 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2721 2722 if (!xstartdraw()) 2723 return; 2724 2725 /* adjust cursor position */ 2726 LIMIT(term.ocx, 0, term.col-1); 2727 LIMIT(term.ocy, 0, term.row-1); 2728 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2729 term.ocx--; 2730 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2731 cx--; 2732 2733 drawregion(0, 0, term.col, term.row); 2734 if (term.scr == 0) 2735 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2736 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2737 term.ocx = cx; 2738 term.ocy = term.c.y; 2739 xfinishdraw(); 2740 if (ocx != term.ocx || ocy != term.ocy) 2741 xximspot(term.ocx, term.ocy); 2742 } 2743 2744 void 2745 redraw(void) 2746 { 2747 tfulldirt(); 2748 draw(); 2749 }