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