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