st.c.orig (58882B)
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 1108 if (copyhist) { 1109 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1110 temp = term.hist[term.histi]; 1111 term.hist[term.histi] = term.line[term.bot]; 1112 term.line[term.bot] = temp; 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 */ 1835 switch (csiescseq.arg[0]) { 1836 case 5: /* Status Report "OK" `0n` */ 1837 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 1838 break; 1839 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ 1840 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1841 term.c.y+1, term.c.x+1); 1842 ttywrite(buf, len, 0); 1843 break; 1844 default: 1845 goto unknown; 1846 } 1847 break; 1848 case 'r': /* DECSTBM -- Set Scrolling Region */ 1849 if (csiescseq.priv) { 1850 goto unknown; 1851 } else { 1852 DEFAULT(csiescseq.arg[0], 1); 1853 DEFAULT(csiescseq.arg[1], term.row); 1854 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1855 tmoveato(0, 0); 1856 } 1857 break; 1858 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1859 tcursor(CURSOR_SAVE); 1860 break; 1861 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1862 tcursor(CURSOR_LOAD); 1863 break; 1864 case ' ': 1865 switch (csiescseq.mode[1]) { 1866 case 'q': /* DECSCUSR -- Set Cursor Style */ 1867 if (xsetcursor(csiescseq.arg[0])) 1868 goto unknown; 1869 break; 1870 default: 1871 goto unknown; 1872 } 1873 break; 1874 } 1875 } 1876 1877 void 1878 csidump(void) 1879 { 1880 size_t i; 1881 uint c; 1882 1883 fprintf(stderr, "ESC["); 1884 for (i = 0; i < csiescseq.len; i++) { 1885 c = csiescseq.buf[i] & 0xff; 1886 if (isprint(c)) { 1887 putc(c, stderr); 1888 } else if (c == '\n') { 1889 fprintf(stderr, "(\\n)"); 1890 } else if (c == '\r') { 1891 fprintf(stderr, "(\\r)"); 1892 } else if (c == 0x1b) { 1893 fprintf(stderr, "(\\e)"); 1894 } else { 1895 fprintf(stderr, "(%02x)", c); 1896 } 1897 } 1898 putc('\n', stderr); 1899 } 1900 1901 void 1902 csireset(void) 1903 { 1904 memset(&csiescseq, 0, sizeof(csiescseq)); 1905 } 1906 1907 void 1908 osc_color_response(int num, int index, int is_osc4) 1909 { 1910 int n; 1911 char buf[32]; 1912 unsigned char r, g, b; 1913 1914 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 1915 fprintf(stderr, "erresc: failed to fetch %s color %d\n", 1916 is_osc4 ? "osc4" : "osc", 1917 is_osc4 ? num : index); 1918 return; 1919 } 1920 1921 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1922 is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 1923 if (n < 0 || n >= sizeof(buf)) { 1924 fprintf(stderr, "error: %s while printing %s response\n", 1925 n < 0 ? "snprintf failed" : "truncation occurred", 1926 is_osc4 ? "osc4" : "osc"); 1927 } else { 1928 ttywrite(buf, n, 1); 1929 } 1930 } 1931 1932 void 1933 strhandle(void) 1934 { 1935 char *p = NULL, *dec; 1936 int j, narg, par; 1937 const struct { int idx; char *str; } osc_table[] = { 1938 { defaultfg, "foreground" }, 1939 { defaultbg, "background" }, 1940 { defaultcs, "cursor" } 1941 }; 1942 1943 term.esc &= ~(ESC_STR_END|ESC_STR); 1944 strparse(); 1945 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1946 1947 switch (strescseq.type) { 1948 case ']': /* OSC -- Operating System Command */ 1949 switch (par) { 1950 case 0: 1951 if (narg > 1) { 1952 xsettitle(strescseq.args[1]); 1953 xseticontitle(strescseq.args[1]); 1954 } 1955 return; 1956 case 1: 1957 if (narg > 1) 1958 xseticontitle(strescseq.args[1]); 1959 return; 1960 case 2: 1961 if (narg > 1) 1962 xsettitle(strescseq.args[1]); 1963 return; 1964 case 52: 1965 if (narg > 2 && allowwindowops) { 1966 dec = base64dec(strescseq.args[2]); 1967 if (dec) { 1968 xsetsel(dec); 1969 xclipcopy(); 1970 } else { 1971 fprintf(stderr, "erresc: invalid base64\n"); 1972 } 1973 } 1974 return; 1975 case 10: 1976 case 11: 1977 case 12: 1978 if (narg < 2) 1979 break; 1980 p = strescseq.args[1]; 1981 if ((j = par - 10) < 0 || j >= LEN(osc_table)) 1982 break; /* shouldn't be possible */ 1983 1984 if (!strcmp(p, "?")) { 1985 osc_color_response(par, osc_table[j].idx, 0); 1986 } else if (xsetcolorname(osc_table[j].idx, p)) { 1987 fprintf(stderr, "erresc: invalid %s color: %s\n", 1988 osc_table[j].str, p); 1989 } else { 1990 tfulldirt(); 1991 } 1992 return; 1993 case 4: /* color set */ 1994 if (narg < 3) 1995 break; 1996 p = strescseq.args[2]; 1997 /* FALLTHROUGH */ 1998 case 104: /* color reset */ 1999 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2000 2001 if (p && !strcmp(p, "?")) { 2002 osc_color_response(j, 0, 1); 2003 } else if (xsetcolorname(j, p)) { 2004 if (par == 104 && narg <= 1) { 2005 xloadcols(); 2006 return; /* color reset without parameter */ 2007 } 2008 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2009 j, p ? p : "(null)"); 2010 } else { 2011 /* 2012 * TODO if defaultbg color is changed, borders 2013 * are dirty 2014 */ 2015 tfulldirt(); 2016 } 2017 return; 2018 } 2019 break; 2020 case 'k': /* old title set compatibility */ 2021 xsettitle(strescseq.args[0]); 2022 return; 2023 case 'P': /* DCS -- Device Control String */ 2024 case '_': /* APC -- Application Program Command */ 2025 case '^': /* PM -- Privacy Message */ 2026 return; 2027 } 2028 2029 fprintf(stderr, "erresc: unknown str "); 2030 strdump(); 2031 } 2032 2033 void 2034 strparse(void) 2035 { 2036 int c; 2037 char *p = strescseq.buf; 2038 2039 strescseq.narg = 0; 2040 strescseq.buf[strescseq.len] = '\0'; 2041 2042 if (*p == '\0') 2043 return; 2044 2045 while (strescseq.narg < STR_ARG_SIZ) { 2046 strescseq.args[strescseq.narg++] = p; 2047 while ((c = *p) != ';' && c != '\0') 2048 ++p; 2049 if (c == '\0') 2050 return; 2051 *p++ = '\0'; 2052 } 2053 } 2054 2055 void 2056 strdump(void) 2057 { 2058 size_t i; 2059 uint c; 2060 2061 fprintf(stderr, "ESC%c", strescseq.type); 2062 for (i = 0; i < strescseq.len; i++) { 2063 c = strescseq.buf[i] & 0xff; 2064 if (c == '\0') { 2065 putc('\n', stderr); 2066 return; 2067 } else if (isprint(c)) { 2068 putc(c, stderr); 2069 } else if (c == '\n') { 2070 fprintf(stderr, "(\\n)"); 2071 } else if (c == '\r') { 2072 fprintf(stderr, "(\\r)"); 2073 } else if (c == 0x1b) { 2074 fprintf(stderr, "(\\e)"); 2075 } else { 2076 fprintf(stderr, "(%02x)", c); 2077 } 2078 } 2079 fprintf(stderr, "ESC\\\n"); 2080 } 2081 2082 void 2083 strreset(void) 2084 { 2085 strescseq = (STREscape){ 2086 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2087 .siz = STR_BUF_SIZ, 2088 }; 2089 } 2090 2091 void 2092 sendbreak(const Arg *arg) 2093 { 2094 if (tcsendbreak(cmdfd, 0)) 2095 perror("Error sending break"); 2096 } 2097 2098 void 2099 tprinter(char *s, size_t len) 2100 { 2101 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2102 perror("Error writing to output file"); 2103 close(iofd); 2104 iofd = -1; 2105 } 2106 } 2107 2108 void 2109 toggleprinter(const Arg *arg) 2110 { 2111 term.mode ^= MODE_PRINT; 2112 } 2113 2114 void 2115 printscreen(const Arg *arg) 2116 { 2117 tdump(); 2118 } 2119 2120 void 2121 printsel(const Arg *arg) 2122 { 2123 tdumpsel(); 2124 } 2125 2126 void 2127 tdumpsel(void) 2128 { 2129 char *ptr; 2130 2131 if ((ptr = getsel())) { 2132 tprinter(ptr, strlen(ptr)); 2133 free(ptr); 2134 } 2135 } 2136 2137 void 2138 tdumpline(int n) 2139 { 2140 char buf[UTF_SIZ]; 2141 const Glyph *bp, *end; 2142 2143 bp = &term.line[n][0]; 2144 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2145 if (bp != end || bp->u != ' ') { 2146 for ( ; bp <= end; ++bp) 2147 tprinter(buf, utf8encode(bp->u, buf)); 2148 } 2149 tprinter("\n", 1); 2150 } 2151 2152 void 2153 tdump(void) 2154 { 2155 int i; 2156 2157 for (i = 0; i < term.row; ++i) 2158 tdumpline(i); 2159 } 2160 2161 void 2162 tputtab(int n) 2163 { 2164 uint x = term.c.x; 2165 2166 if (n > 0) { 2167 while (x < term.col && n--) 2168 for (++x; x < term.col && !term.tabs[x]; ++x) 2169 /* nothing */ ; 2170 } else if (n < 0) { 2171 while (x > 0 && n++) 2172 for (--x; x > 0 && !term.tabs[x]; --x) 2173 /* nothing */ ; 2174 } 2175 term.c.x = LIMIT(x, 0, term.col-1); 2176 } 2177 2178 void 2179 tdefutf8(char ascii) 2180 { 2181 if (ascii == 'G') 2182 term.mode |= MODE_UTF8; 2183 else if (ascii == '@') 2184 term.mode &= ~MODE_UTF8; 2185 } 2186 2187 void 2188 tdeftran(char ascii) 2189 { 2190 static char cs[] = "0B"; 2191 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2192 char *p; 2193 2194 if ((p = strchr(cs, ascii)) == NULL) { 2195 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2196 } else { 2197 term.trantbl[term.icharset] = vcs[p - cs]; 2198 } 2199 } 2200 2201 void 2202 tdectest(char c) 2203 { 2204 int x, y; 2205 2206 if (c == '8') { /* DEC screen alignment test. */ 2207 for (x = 0; x < term.col; ++x) { 2208 for (y = 0; y < term.row; ++y) 2209 tsetchar('E', &term.c.attr, x, y); 2210 } 2211 } 2212 } 2213 2214 void 2215 tstrsequence(uchar c) 2216 { 2217 switch (c) { 2218 case 0x90: /* DCS -- Device Control String */ 2219 c = 'P'; 2220 break; 2221 case 0x9f: /* APC -- Application Program Command */ 2222 c = '_'; 2223 break; 2224 case 0x9e: /* PM -- Privacy Message */ 2225 c = '^'; 2226 break; 2227 case 0x9d: /* OSC -- Operating System Command */ 2228 c = ']'; 2229 break; 2230 } 2231 strreset(); 2232 strescseq.type = c; 2233 term.esc |= ESC_STR; 2234 } 2235 2236 void 2237 tcontrolcode(uchar ascii) 2238 { 2239 switch (ascii) { 2240 case '\t': /* HT */ 2241 tputtab(1); 2242 return; 2243 case '\b': /* BS */ 2244 tmoveto(term.c.x-1, term.c.y); 2245 return; 2246 case '\r': /* CR */ 2247 tmoveto(0, term.c.y); 2248 return; 2249 case '\f': /* LF */ 2250 case '\v': /* VT */ 2251 case '\n': /* LF */ 2252 /* go to first col if the mode is set */ 2253 tnewline(IS_SET(MODE_CRLF)); 2254 return; 2255 case '\a': /* BEL */ 2256 if (term.esc & ESC_STR_END) { 2257 /* backwards compatibility to xterm */ 2258 strhandle(); 2259 } else { 2260 xbell(); 2261 } 2262 break; 2263 case '\033': /* ESC */ 2264 csireset(); 2265 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2266 term.esc |= ESC_START; 2267 return; 2268 case '\016': /* SO (LS1 -- Locking shift 1) */ 2269 case '\017': /* SI (LS0 -- Locking shift 0) */ 2270 term.charset = 1 - (ascii - '\016'); 2271 return; 2272 case '\032': /* SUB */ 2273 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2274 /* FALLTHROUGH */ 2275 case '\030': /* CAN */ 2276 csireset(); 2277 break; 2278 case '\005': /* ENQ (IGNORED) */ 2279 case '\000': /* NUL (IGNORED) */ 2280 case '\021': /* XON (IGNORED) */ 2281 case '\023': /* XOFF (IGNORED) */ 2282 case 0177: /* DEL (IGNORED) */ 2283 return; 2284 case 0x80: /* TODO: PAD */ 2285 case 0x81: /* TODO: HOP */ 2286 case 0x82: /* TODO: BPH */ 2287 case 0x83: /* TODO: NBH */ 2288 case 0x84: /* TODO: IND */ 2289 break; 2290 case 0x85: /* NEL -- Next line */ 2291 tnewline(1); /* always go to first col */ 2292 break; 2293 case 0x86: /* TODO: SSA */ 2294 case 0x87: /* TODO: ESA */ 2295 break; 2296 case 0x88: /* HTS -- Horizontal tab stop */ 2297 term.tabs[term.c.x] = 1; 2298 break; 2299 case 0x89: /* TODO: HTJ */ 2300 case 0x8a: /* TODO: VTS */ 2301 case 0x8b: /* TODO: PLD */ 2302 case 0x8c: /* TODO: PLU */ 2303 case 0x8d: /* TODO: RI */ 2304 case 0x8e: /* TODO: SS2 */ 2305 case 0x8f: /* TODO: SS3 */ 2306 case 0x91: /* TODO: PU1 */ 2307 case 0x92: /* TODO: PU2 */ 2308 case 0x93: /* TODO: STS */ 2309 case 0x94: /* TODO: CCH */ 2310 case 0x95: /* TODO: MW */ 2311 case 0x96: /* TODO: SPA */ 2312 case 0x97: /* TODO: EPA */ 2313 case 0x98: /* TODO: SOS */ 2314 case 0x99: /* TODO: SGCI */ 2315 break; 2316 case 0x9a: /* DECID -- Identify Terminal */ 2317 ttywrite(vtiden, strlen(vtiden), 0); 2318 break; 2319 case 0x9b: /* TODO: CSI */ 2320 case 0x9c: /* TODO: ST */ 2321 break; 2322 case 0x90: /* DCS -- Device Control String */ 2323 case 0x9d: /* OSC -- Operating System Command */ 2324 case 0x9e: /* PM -- Privacy Message */ 2325 case 0x9f: /* APC -- Application Program Command */ 2326 tstrsequence(ascii); 2327 return; 2328 } 2329 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2330 term.esc &= ~(ESC_STR_END|ESC_STR); 2331 } 2332 2333 /* 2334 * returns 1 when the sequence is finished and it hasn't to read 2335 * more characters for this sequence, otherwise 0 2336 */ 2337 int 2338 eschandle(uchar ascii) 2339 { 2340 switch (ascii) { 2341 case '[': 2342 term.esc |= ESC_CSI; 2343 return 0; 2344 case '#': 2345 term.esc |= ESC_TEST; 2346 return 0; 2347 case '%': 2348 term.esc |= ESC_UTF8; 2349 return 0; 2350 case 'P': /* DCS -- Device Control String */ 2351 case '_': /* APC -- Application Program Command */ 2352 case '^': /* PM -- Privacy Message */ 2353 case ']': /* OSC -- Operating System Command */ 2354 case 'k': /* old title set compatibility */ 2355 tstrsequence(ascii); 2356 return 0; 2357 case 'n': /* LS2 -- Locking shift 2 */ 2358 case 'o': /* LS3 -- Locking shift 3 */ 2359 term.charset = 2 + (ascii - 'n'); 2360 break; 2361 case '(': /* GZD4 -- set primary charset G0 */ 2362 case ')': /* G1D4 -- set secondary charset G1 */ 2363 case '*': /* G2D4 -- set tertiary charset G2 */ 2364 case '+': /* G3D4 -- set quaternary charset G3 */ 2365 term.icharset = ascii - '('; 2366 term.esc |= ESC_ALTCHARSET; 2367 return 0; 2368 case 'D': /* IND -- Linefeed */ 2369 if (term.c.y == term.bot) { 2370 tscrollup(term.top, 1, 1); 2371 } else { 2372 tmoveto(term.c.x, term.c.y+1); 2373 } 2374 break; 2375 case 'E': /* NEL -- Next line */ 2376 tnewline(1); /* always go to first col */ 2377 break; 2378 case 'H': /* HTS -- Horizontal tab stop */ 2379 term.tabs[term.c.x] = 1; 2380 break; 2381 case 'M': /* RI -- Reverse index */ 2382 if (term.c.y == term.top) { 2383 tscrolldown(term.top, 1, 1); 2384 } else { 2385 tmoveto(term.c.x, term.c.y-1); 2386 } 2387 break; 2388 case 'Z': /* DECID -- Identify Terminal */ 2389 ttywrite(vtiden, strlen(vtiden), 0); 2390 break; 2391 case 'c': /* RIS -- Reset to initial state */ 2392 treset(); 2393 resettitle(); 2394 xloadcols(); 2395 break; 2396 case '=': /* DECPAM -- Application keypad */ 2397 xsetmode(1, MODE_APPKEYPAD); 2398 break; 2399 case '>': /* DECPNM -- Normal keypad */ 2400 xsetmode(0, MODE_APPKEYPAD); 2401 break; 2402 case '7': /* DECSC -- Save Cursor */ 2403 tcursor(CURSOR_SAVE); 2404 break; 2405 case '8': /* DECRC -- Restore Cursor */ 2406 tcursor(CURSOR_LOAD); 2407 break; 2408 case '\\': /* ST -- String Terminator */ 2409 if (term.esc & ESC_STR_END) 2410 strhandle(); 2411 break; 2412 default: 2413 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2414 (uchar) ascii, isprint(ascii)? ascii:'.'); 2415 break; 2416 } 2417 return 1; 2418 } 2419 2420 void 2421 tputc(Rune u) 2422 { 2423 char c[UTF_SIZ]; 2424 int control; 2425 int width, len; 2426 Glyph *gp; 2427 2428 control = ISCONTROL(u); 2429 if (u < 127 || !IS_SET(MODE_UTF8)) { 2430 c[0] = u; 2431 width = len = 1; 2432 } else { 2433 len = utf8encode(u, c); 2434 if (!control && (width = wcwidth(u)) == -1) 2435 width = 1; 2436 } 2437 2438 if (IS_SET(MODE_PRINT)) 2439 tprinter(c, len); 2440 2441 /* 2442 * STR sequence must be checked before anything else 2443 * because it uses all following characters until it 2444 * receives a ESC, a SUB, a ST or any other C1 control 2445 * character. 2446 */ 2447 if (term.esc & ESC_STR) { 2448 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2449 ISCONTROLC1(u)) { 2450 term.esc &= ~(ESC_START|ESC_STR); 2451 term.esc |= ESC_STR_END; 2452 goto check_control_code; 2453 } 2454 2455 if (strescseq.len+len >= strescseq.siz) { 2456 /* 2457 * Here is a bug in terminals. If the user never sends 2458 * some code to stop the str or esc command, then st 2459 * will stop responding. But this is better than 2460 * silently failing with unknown characters. At least 2461 * then users will report back. 2462 * 2463 * In the case users ever get fixed, here is the code: 2464 */ 2465 /* 2466 * term.esc = 0; 2467 * strhandle(); 2468 */ 2469 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2470 return; 2471 strescseq.siz *= 2; 2472 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2473 } 2474 2475 memmove(&strescseq.buf[strescseq.len], c, len); 2476 strescseq.len += len; 2477 return; 2478 } 2479 2480 check_control_code: 2481 /* 2482 * Actions of control codes must be performed as soon they arrive 2483 * because they can be embedded inside a control sequence, and 2484 * they must not cause conflicts with sequences. 2485 */ 2486 if (control) { 2487 /* in UTF-8 mode ignore handling C1 control characters */ 2488 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2489 return; 2490 tcontrolcode(u); 2491 /* 2492 * control codes are not shown ever 2493 */ 2494 if (!term.esc) 2495 term.lastc = 0; 2496 return; 2497 } else if (term.esc & ESC_START) { 2498 if (term.esc & ESC_CSI) { 2499 csiescseq.buf[csiescseq.len++] = u; 2500 if (BETWEEN(u, 0x40, 0x7E) 2501 || csiescseq.len >= \ 2502 sizeof(csiescseq.buf)-1) { 2503 term.esc = 0; 2504 csiparse(); 2505 csihandle(); 2506 } 2507 return; 2508 } else if (term.esc & ESC_UTF8) { 2509 tdefutf8(u); 2510 } else if (term.esc & ESC_ALTCHARSET) { 2511 tdeftran(u); 2512 } else if (term.esc & ESC_TEST) { 2513 tdectest(u); 2514 } else { 2515 if (!eschandle(u)) 2516 return; 2517 /* sequence already finished */ 2518 } 2519 term.esc = 0; 2520 /* 2521 * All characters which form part of a sequence are not 2522 * printed 2523 */ 2524 return; 2525 } 2526 if (selected(term.c.x, term.c.y)) 2527 selclear(); 2528 2529 gp = &term.line[term.c.y][term.c.x]; 2530 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2531 gp->mode |= ATTR_WRAP; 2532 tnewline(1); 2533 gp = &term.line[term.c.y][term.c.x]; 2534 } 2535 2536 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2537 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2538 gp->mode &= ~ATTR_WIDE; 2539 } 2540 2541 if (term.c.x+width > term.col) { 2542 tnewline(1); 2543 gp = &term.line[term.c.y][term.c.x]; 2544 } 2545 2546 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2547 term.lastc = u; 2548 2549 if (width == 2) { 2550 gp->mode |= ATTR_WIDE; 2551 if (term.c.x+1 < term.col) { 2552 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2553 gp[2].u = ' '; 2554 gp[2].mode &= ~ATTR_WDUMMY; 2555 } 2556 gp[1].u = '\0'; 2557 gp[1].mode = ATTR_WDUMMY; 2558 } 2559 } 2560 if (term.c.x+width < term.col) { 2561 tmoveto(term.c.x+width, term.c.y); 2562 } else { 2563 term.c.state |= CURSOR_WRAPNEXT; 2564 } 2565 } 2566 2567 int 2568 twrite(const char *buf, int buflen, int show_ctrl) 2569 { 2570 int charsize; 2571 Rune u; 2572 int n; 2573 2574 for (n = 0; n < buflen; n += charsize) { 2575 if (IS_SET(MODE_UTF8)) { 2576 /* process a complete utf8 char */ 2577 charsize = utf8decode(buf + n, &u, buflen - n); 2578 if (charsize == 0) 2579 break; 2580 } else { 2581 u = buf[n] & 0xFF; 2582 charsize = 1; 2583 } 2584 if (show_ctrl && ISCONTROL(u)) { 2585 if (u & 0x80) { 2586 u &= 0x7f; 2587 tputc('^'); 2588 tputc('['); 2589 } else if (u != '\n' && u != '\r' && u != '\t') { 2590 u ^= 0x40; 2591 tputc('^'); 2592 } 2593 } 2594 tputc(u); 2595 } 2596 return n; 2597 } 2598 2599 void 2600 tresize(int col, int row) 2601 { 2602 int i, j; 2603 int minrow = MIN(row, term.row); 2604 int mincol = MIN(col, term.col); 2605 int *bp; 2606 TCursor c; 2607 2608 if (col < 1 || row < 1) { 2609 fprintf(stderr, 2610 "tresize: error resizing to %dx%d\n", col, row); 2611 return; 2612 } 2613 2614 /* 2615 * slide screen to keep cursor where we expect it - 2616 * tscrollup would work here, but we can optimize to 2617 * memmove because we're freeing the earlier lines 2618 */ 2619 for (i = 0; i <= term.c.y - row; i++) { 2620 free(term.line[i]); 2621 free(term.alt[i]); 2622 } 2623 /* ensure that both src and dst are not NULL */ 2624 if (i > 0) { 2625 memmove(term.line, term.line + i, row * sizeof(Line)); 2626 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2627 } 2628 for (i += row; i < term.row; i++) { 2629 free(term.line[i]); 2630 free(term.alt[i]); 2631 } 2632 2633 /* resize to new height */ 2634 term.line = xrealloc(term.line, row * sizeof(Line)); 2635 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2636 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2637 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2638 2639 for (i = 0; i < HISTSIZE; i++) { 2640 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2641 for (j = mincol; j < col; j++) { 2642 term.hist[i][j] = term.c.attr; 2643 term.hist[i][j].u = ' '; 2644 } 2645 } 2646 2647 /* resize each row to new width, zero-pad if needed */ 2648 for (i = 0; i < minrow; i++) { 2649 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2650 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2651 } 2652 2653 /* allocate any new rows */ 2654 for (/* i = minrow */; i < row; i++) { 2655 term.line[i] = xmalloc(col * sizeof(Glyph)); 2656 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2657 } 2658 if (col > term.col) { 2659 bp = term.tabs + term.col; 2660 2661 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2662 while (--bp > term.tabs && !*bp) 2663 /* nothing */ ; 2664 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2665 *bp = 1; 2666 } 2667 /* update terminal size */ 2668 term.col = col; 2669 term.row = row; 2670 /* reset scrolling region */ 2671 tsetscroll(0, row-1); 2672 /* make use of the LIMIT in tmoveto */ 2673 tmoveto(term.c.x, term.c.y); 2674 /* Clearing both screens (it makes dirty all lines) */ 2675 c = term.c; 2676 for (i = 0; i < 2; i++) { 2677 if (mincol < col && 0 < minrow) { 2678 tclearregion(mincol, 0, col - 1, minrow - 1); 2679 } 2680 if (0 < col && minrow < row) { 2681 tclearregion(0, minrow, col - 1, row - 1); 2682 } 2683 tswapscreen(); 2684 tcursor(CURSOR_LOAD); 2685 } 2686 term.c = c; 2687 } 2688 2689 void 2690 resettitle(void) 2691 { 2692 xsettitle(NULL); 2693 } 2694 2695 void 2696 drawregion(int x1, int y1, int x2, int y2) 2697 { 2698 int y; 2699 2700 for (y = y1; y < y2; y++) { 2701 if (!term.dirty[y]) 2702 continue; 2703 2704 term.dirty[y] = 0; 2705 xdrawline(TLINE(y), x1, y, x2); 2706 } 2707 } 2708 2709 void 2710 draw(void) 2711 { 2712 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2713 2714 if (!xstartdraw()) 2715 return; 2716 2717 /* adjust cursor position */ 2718 LIMIT(term.ocx, 0, term.col-1); 2719 LIMIT(term.ocy, 0, term.row-1); 2720 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2721 term.ocx--; 2722 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2723 cx--; 2724 2725 drawregion(0, 0, term.col, term.row); 2726 if (term.scr == 0) 2727 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2728 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2729 term.ocx = cx; 2730 term.ocy = term.c.y; 2731 xfinishdraw(); 2732 if (ocx != term.ocx || ocy != term.ocy) 2733 xximspot(term.ocx, term.ocy); 2734 } 2735 2736 void 2737 redraw(void) 2738 { 2739 tfulldirt(); 2740 draw(); 2741 }