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