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