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