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