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