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