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