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