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