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