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