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