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