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