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