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