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