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