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