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