st.c (57214B)
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 void 1061 tswapscreen(void) 1062 { 1063 Line *tmp = term.line; 1064 1065 term.line = term.alt; 1066 term.alt = tmp; 1067 term.mode ^= MODE_ALTSCREEN; 1068 tfulldirt(); 1069 } 1070 1071 void 1072 kscrolldown(const Arg* a) 1073 { 1074 int n = a->i; 1075 1076 if (n < 0) 1077 n = term.row + n; 1078 1079 if (n > term.scr) 1080 n = term.scr; 1081 1082 if (term.scr > 0) { 1083 term.scr -= n; 1084 selscroll(0, -n); 1085 tfulldirt(); 1086 } 1087 } 1088 1089 void 1090 kscrollup(const Arg* a) 1091 { 1092 int n = a->i; 1093 1094 if (n < 0) 1095 n = term.row + n; 1096 1097 if (term.scr <= HISTSIZE-n) { 1098 term.scr += n; 1099 selscroll(0, n); 1100 tfulldirt(); 1101 } 1102 } 1103 1104 void 1105 tscrolldown(int orig, int n, int copyhist) 1106 { 1107 int i; 1108 Line temp; 1109 1110 LIMIT(n, 0, term.bot-orig+1); 1111 1112 if (copyhist) { 1113 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1114 temp = term.hist[term.histi]; 1115 term.hist[term.histi] = term.line[term.bot]; 1116 term.line[term.bot] = temp; 1117 } 1118 1119 tsetdirt(orig, term.bot-n); 1120 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1121 1122 for (i = term.bot; i >= orig+n; i--) { 1123 temp = term.line[i]; 1124 term.line[i] = term.line[i-n]; 1125 term.line[i-n] = temp; 1126 } 1127 1128 if (term.scr == 0) 1129 selscroll(orig, n); 1130 } 1131 1132 void 1133 tscrollup(int orig, int n, int copyhist) 1134 { 1135 int i; 1136 Line temp; 1137 1138 LIMIT(n, 0, term.bot-orig+1); 1139 1140 if (copyhist) { 1141 term.histi = (term.histi + 1) % HISTSIZE; 1142 temp = term.hist[term.histi]; 1143 term.hist[term.histi] = term.line[orig]; 1144 term.line[orig] = temp; 1145 } 1146 1147 if (term.scr > 0 && term.scr < HISTSIZE) 1148 term.scr = MIN(term.scr + n, HISTSIZE-1); 1149 1150 tclearregion(0, orig, term.col-1, orig+n-1); 1151 tsetdirt(orig+n, term.bot); 1152 1153 for (i = orig; i <= term.bot-n; i++) { 1154 temp = term.line[i]; 1155 term.line[i] = term.line[i+n]; 1156 term.line[i+n] = temp; 1157 } 1158 1159 if (term.scr == 0) 1160 selscroll(orig, -n); 1161 } 1162 1163 void 1164 selscroll(int orig, int n) 1165 { 1166 if (sel.ob.x == -1) 1167 return; 1168 1169 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1170 selclear(); 1171 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1172 sel.ob.y += n; 1173 sel.oe.y += n; 1174 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1175 sel.oe.y < term.top || sel.oe.y > term.bot) { 1176 selclear(); 1177 } else { 1178 selnormalize(); 1179 } 1180 } 1181 } 1182 1183 void 1184 tnewline(int first_col) 1185 { 1186 int y = term.c.y; 1187 1188 if (y == term.bot) { 1189 tscrollup(term.top, 1, 1); 1190 } else { 1191 y++; 1192 } 1193 tmoveto(first_col ? 0 : term.c.x, y); 1194 } 1195 1196 void 1197 csiparse(void) 1198 { 1199 char *p = csiescseq.buf, *np; 1200 long int v; 1201 1202 csiescseq.narg = 0; 1203 if (*p == '?') { 1204 csiescseq.priv = 1; 1205 p++; 1206 } 1207 1208 csiescseq.buf[csiescseq.len] = '\0'; 1209 while (p < csiescseq.buf+csiescseq.len) { 1210 np = NULL; 1211 v = strtol(p, &np, 10); 1212 if (np == p) 1213 v = 0; 1214 if (v == LONG_MAX || v == LONG_MIN) 1215 v = -1; 1216 csiescseq.arg[csiescseq.narg++] = v; 1217 p = np; 1218 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1219 break; 1220 p++; 1221 } 1222 csiescseq.mode[0] = *p++; 1223 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1224 } 1225 1226 /* for absolute user moves, when decom is set */ 1227 void 1228 tmoveato(int x, int y) 1229 { 1230 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1231 } 1232 1233 void 1234 tmoveto(int x, int y) 1235 { 1236 int miny, maxy; 1237 1238 if (term.c.state & CURSOR_ORIGIN) { 1239 miny = term.top; 1240 maxy = term.bot; 1241 } else { 1242 miny = 0; 1243 maxy = term.row - 1; 1244 } 1245 term.c.state &= ~CURSOR_WRAPNEXT; 1246 term.c.x = LIMIT(x, 0, term.col-1); 1247 term.c.y = LIMIT(y, miny, maxy); 1248 } 1249 1250 void 1251 tsetchar(Rune u, Glyph *attr, int x, int y) 1252 { 1253 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1254 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1255 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1256 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1257 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1258 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1259 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1260 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1261 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1262 }; 1263 1264 /* 1265 * The table is proudly stolen from rxvt. 1266 */ 1267 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1268 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1269 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1270 1271 if (term.line[y][x].mode & ATTR_WIDE) { 1272 if (x+1 < term.col) { 1273 term.line[y][x+1].u = ' '; 1274 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1275 } 1276 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1277 term.line[y][x-1].u = ' '; 1278 term.line[y][x-1].mode &= ~ATTR_WIDE; 1279 } 1280 1281 term.dirty[y] = 1; 1282 term.line[y][x] = *attr; 1283 term.line[y][x].u = u; 1284 } 1285 1286 void 1287 tclearregion(int x1, int y1, int x2, int y2) 1288 { 1289 int x, y, temp; 1290 Glyph *gp; 1291 1292 if (x1 > x2) 1293 temp = x1, x1 = x2, x2 = temp; 1294 if (y1 > y2) 1295 temp = y1, y1 = y2, y2 = temp; 1296 1297 LIMIT(x1, 0, term.col-1); 1298 LIMIT(x2, 0, term.col-1); 1299 LIMIT(y1, 0, term.row-1); 1300 LIMIT(y2, 0, term.row-1); 1301 1302 for (y = y1; y <= y2; y++) { 1303 term.dirty[y] = 1; 1304 for (x = x1; x <= x2; x++) { 1305 gp = &term.line[y][x]; 1306 if (selected(x, y)) 1307 selclear(); 1308 gp->fg = term.c.attr.fg; 1309 gp->bg = term.c.attr.bg; 1310 gp->mode = 0; 1311 gp->u = ' '; 1312 } 1313 } 1314 } 1315 1316 void 1317 tdeletechar(int n) 1318 { 1319 int dst, src, size; 1320 Glyph *line; 1321 1322 LIMIT(n, 0, term.col - term.c.x); 1323 1324 dst = term.c.x; 1325 src = term.c.x + n; 1326 size = term.col - src; 1327 line = term.line[term.c.y]; 1328 1329 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1330 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1331 } 1332 1333 void 1334 tinsertblank(int n) 1335 { 1336 int dst, src, size; 1337 Glyph *line; 1338 1339 LIMIT(n, 0, term.col - term.c.x); 1340 1341 dst = term.c.x + n; 1342 src = term.c.x; 1343 size = term.col - dst; 1344 line = term.line[term.c.y]; 1345 1346 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1347 tclearregion(src, term.c.y, dst - 1, term.c.y); 1348 } 1349 1350 void 1351 tinsertblankline(int n) 1352 { 1353 if (BETWEEN(term.c.y, term.top, term.bot)) 1354 tscrolldown(term.c.y, n, 0); 1355 } 1356 1357 void 1358 tdeleteline(int n) 1359 { 1360 if (BETWEEN(term.c.y, term.top, term.bot)) 1361 tscrollup(term.c.y, n, 0); 1362 } 1363 1364 int32_t 1365 tdefcolor(int *attr, int *npar, int l) 1366 { 1367 int32_t idx = -1; 1368 uint r, g, b; 1369 1370 switch (attr[*npar + 1]) { 1371 case 2: /* direct color in RGB space */ 1372 if (*npar + 4 >= l) { 1373 fprintf(stderr, 1374 "erresc(38): Incorrect number of parameters (%d)\n", 1375 *npar); 1376 break; 1377 } 1378 r = attr[*npar + 2]; 1379 g = attr[*npar + 3]; 1380 b = attr[*npar + 4]; 1381 *npar += 4; 1382 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1383 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1384 r, g, b); 1385 else 1386 idx = TRUECOLOR(r, g, b); 1387 break; 1388 case 5: /* indexed color */ 1389 if (*npar + 2 >= l) { 1390 fprintf(stderr, 1391 "erresc(38): Incorrect number of parameters (%d)\n", 1392 *npar); 1393 break; 1394 } 1395 *npar += 2; 1396 if (!BETWEEN(attr[*npar], 0, 255)) 1397 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1398 else 1399 idx = attr[*npar]; 1400 break; 1401 case 0: /* implemented defined (only foreground) */ 1402 case 1: /* transparent */ 1403 case 3: /* direct color in CMY space */ 1404 case 4: /* direct color in CMYK space */ 1405 default: 1406 fprintf(stderr, 1407 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1408 break; 1409 } 1410 1411 return idx; 1412 } 1413 1414 void 1415 tsetattr(int *attr, int l) 1416 { 1417 int i; 1418 int32_t idx; 1419 1420 for (i = 0; i < l; i++) { 1421 switch (attr[i]) { 1422 case 0: 1423 term.c.attr.mode &= ~( 1424 ATTR_BOLD | 1425 ATTR_FAINT | 1426 ATTR_ITALIC | 1427 ATTR_UNDERLINE | 1428 ATTR_BLINK | 1429 ATTR_REVERSE | 1430 ATTR_INVISIBLE | 1431 ATTR_STRUCK ); 1432 term.c.attr.fg = defaultfg; 1433 term.c.attr.bg = defaultbg; 1434 break; 1435 case 1: 1436 term.c.attr.mode |= ATTR_BOLD; 1437 break; 1438 case 2: 1439 term.c.attr.mode |= ATTR_FAINT; 1440 break; 1441 case 3: 1442 term.c.attr.mode |= ATTR_ITALIC; 1443 break; 1444 case 4: 1445 term.c.attr.mode |= ATTR_UNDERLINE; 1446 break; 1447 case 5: /* slow blink */ 1448 /* FALLTHROUGH */ 1449 case 6: /* rapid blink */ 1450 term.c.attr.mode |= ATTR_BLINK; 1451 break; 1452 case 7: 1453 term.c.attr.mode |= ATTR_REVERSE; 1454 break; 1455 case 8: 1456 term.c.attr.mode |= ATTR_INVISIBLE; 1457 break; 1458 case 9: 1459 term.c.attr.mode |= ATTR_STRUCK; 1460 break; 1461 case 22: 1462 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1463 break; 1464 case 23: 1465 term.c.attr.mode &= ~ATTR_ITALIC; 1466 break; 1467 case 24: 1468 term.c.attr.mode &= ~ATTR_UNDERLINE; 1469 break; 1470 case 25: 1471 term.c.attr.mode &= ~ATTR_BLINK; 1472 break; 1473 case 27: 1474 term.c.attr.mode &= ~ATTR_REVERSE; 1475 break; 1476 case 28: 1477 term.c.attr.mode &= ~ATTR_INVISIBLE; 1478 break; 1479 case 29: 1480 term.c.attr.mode &= ~ATTR_STRUCK; 1481 break; 1482 case 38: 1483 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1484 term.c.attr.fg = idx; 1485 break; 1486 case 39: 1487 term.c.attr.fg = defaultfg; 1488 break; 1489 case 48: 1490 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1491 term.c.attr.bg = idx; 1492 break; 1493 case 49: 1494 term.c.attr.bg = defaultbg; 1495 break; 1496 default: 1497 if (BETWEEN(attr[i], 30, 37)) { 1498 term.c.attr.fg = attr[i] - 30; 1499 } else if (BETWEEN(attr[i], 40, 47)) { 1500 term.c.attr.bg = attr[i] - 40; 1501 } else if (BETWEEN(attr[i], 90, 97)) { 1502 term.c.attr.fg = attr[i] - 90 + 8; 1503 } else if (BETWEEN(attr[i], 100, 107)) { 1504 term.c.attr.bg = attr[i] - 100 + 8; 1505 } else { 1506 fprintf(stderr, 1507 "erresc(default): gfx attr %d unknown\n", 1508 attr[i]); 1509 csidump(); 1510 } 1511 break; 1512 } 1513 } 1514 } 1515 1516 void 1517 tsetscroll(int t, int b) 1518 { 1519 int temp; 1520 1521 LIMIT(t, 0, term.row-1); 1522 LIMIT(b, 0, term.row-1); 1523 if (t > b) { 1524 temp = t; 1525 t = b; 1526 b = temp; 1527 } 1528 term.top = t; 1529 term.bot = b; 1530 } 1531 1532 void 1533 tsetmode(int priv, int set, int *args, int narg) 1534 { 1535 int alt, *lim; 1536 1537 for (lim = args + narg; args < lim; ++args) { 1538 if (priv) { 1539 switch (*args) { 1540 case 1: /* DECCKM -- Cursor key */ 1541 xsetmode(set, MODE_APPCURSOR); 1542 break; 1543 case 5: /* DECSCNM -- Reverse video */ 1544 xsetmode(set, MODE_REVERSE); 1545 break; 1546 case 6: /* DECOM -- Origin */ 1547 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1548 tmoveato(0, 0); 1549 break; 1550 case 7: /* DECAWM -- Auto wrap */ 1551 MODBIT(term.mode, set, MODE_WRAP); 1552 break; 1553 case 0: /* Error (IGNORED) */ 1554 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1555 case 3: /* DECCOLM -- Column (IGNORED) */ 1556 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1557 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1558 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1559 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1560 case 42: /* DECNRCM -- National characters (IGNORED) */ 1561 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1562 break; 1563 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1564 xsetmode(!set, MODE_HIDE); 1565 break; 1566 case 9: /* X10 mouse compatibility mode */ 1567 xsetpointermotion(0); 1568 xsetmode(0, MODE_MOUSE); 1569 xsetmode(set, MODE_MOUSEX10); 1570 break; 1571 case 1000: /* 1000: report button press */ 1572 xsetpointermotion(0); 1573 xsetmode(0, MODE_MOUSE); 1574 xsetmode(set, MODE_MOUSEBTN); 1575 break; 1576 case 1002: /* 1002: report motion on button press */ 1577 xsetpointermotion(0); 1578 xsetmode(0, MODE_MOUSE); 1579 xsetmode(set, MODE_MOUSEMOTION); 1580 break; 1581 case 1003: /* 1003: enable all mouse motions */ 1582 xsetpointermotion(set); 1583 xsetmode(0, MODE_MOUSE); 1584 xsetmode(set, MODE_MOUSEMANY); 1585 break; 1586 case 1004: /* 1004: send focus events to tty */ 1587 xsetmode(set, MODE_FOCUS); 1588 break; 1589 case 1006: /* 1006: extended reporting mode */ 1590 xsetmode(set, MODE_MOUSESGR); 1591 break; 1592 case 1034: 1593 xsetmode(set, MODE_8BIT); 1594 break; 1595 case 1049: /* swap screen & set/restore cursor as xterm */ 1596 if (!allowaltscreen) 1597 break; 1598 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1599 /* FALLTHROUGH */ 1600 case 47: /* swap screen */ 1601 case 1047: 1602 if (!allowaltscreen) 1603 break; 1604 alt = IS_SET(MODE_ALTSCREEN); 1605 if (alt) { 1606 tclearregion(0, 0, term.col-1, 1607 term.row-1); 1608 } 1609 if (set ^ alt) /* set is always 1 or 0 */ 1610 tswapscreen(); 1611 if (*args != 1049) 1612 break; 1613 /* FALLTHROUGH */ 1614 case 1048: 1615 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1616 break; 1617 case 2004: /* 2004: bracketed paste mode */ 1618 xsetmode(set, MODE_BRCKTPASTE); 1619 break; 1620 /* Not implemented mouse modes. See comments there. */ 1621 case 1001: /* mouse highlight mode; can hang the 1622 terminal by design when implemented. */ 1623 case 1005: /* UTF-8 mouse mode; will confuse 1624 applications not supporting UTF-8 1625 and luit. */ 1626 case 1015: /* urxvt mangled mouse mode; incompatible 1627 and can be mistaken for other control 1628 codes. */ 1629 break; 1630 default: 1631 fprintf(stderr, 1632 "erresc: unknown private set/reset mode %d\n", 1633 *args); 1634 break; 1635 } 1636 } else { 1637 switch (*args) { 1638 case 0: /* Error (IGNORED) */ 1639 break; 1640 case 2: 1641 xsetmode(set, MODE_KBDLOCK); 1642 break; 1643 case 4: /* IRM -- Insertion-replacement */ 1644 MODBIT(term.mode, set, MODE_INSERT); 1645 break; 1646 case 12: /* SRM -- Send/Receive */ 1647 MODBIT(term.mode, !set, MODE_ECHO); 1648 break; 1649 case 20: /* LNM -- Linefeed/new line */ 1650 MODBIT(term.mode, set, MODE_CRLF); 1651 break; 1652 default: 1653 fprintf(stderr, 1654 "erresc: unknown set/reset mode %d\n", 1655 *args); 1656 break; 1657 } 1658 } 1659 } 1660 } 1661 1662 void 1663 csihandle(void) 1664 { 1665 char buf[40]; 1666 int len; 1667 1668 switch (csiescseq.mode[0]) { 1669 default: 1670 unknown: 1671 fprintf(stderr, "erresc: unknown csi "); 1672 csidump(); 1673 /* die(""); */ 1674 break; 1675 case '@': /* ICH -- Insert <n> blank char */ 1676 DEFAULT(csiescseq.arg[0], 1); 1677 tinsertblank(csiescseq.arg[0]); 1678 break; 1679 case 'A': /* CUU -- Cursor <n> Up */ 1680 DEFAULT(csiescseq.arg[0], 1); 1681 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1682 break; 1683 case 'B': /* CUD -- Cursor <n> Down */ 1684 case 'e': /* VPR --Cursor <n> Down */ 1685 DEFAULT(csiescseq.arg[0], 1); 1686 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1687 break; 1688 case 'i': /* MC -- Media Copy */ 1689 switch (csiescseq.arg[0]) { 1690 case 0: 1691 tdump(); 1692 break; 1693 case 1: 1694 tdumpline(term.c.y); 1695 break; 1696 case 2: 1697 tdumpsel(); 1698 break; 1699 case 4: 1700 term.mode &= ~MODE_PRINT; 1701 break; 1702 case 5: 1703 term.mode |= MODE_PRINT; 1704 break; 1705 } 1706 break; 1707 case 'c': /* DA -- Device Attributes */ 1708 if (csiescseq.arg[0] == 0) 1709 ttywrite(vtiden, strlen(vtiden), 0); 1710 break; 1711 case 'b': /* REP -- if last char is printable print it <n> more times */ 1712 DEFAULT(csiescseq.arg[0], 1); 1713 if (term.lastc) 1714 while (csiescseq.arg[0]-- > 0) 1715 tputc(term.lastc); 1716 break; 1717 case 'C': /* CUF -- Cursor <n> Forward */ 1718 case 'a': /* HPR -- Cursor <n> Forward */ 1719 DEFAULT(csiescseq.arg[0], 1); 1720 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1721 break; 1722 case 'D': /* CUB -- Cursor <n> Backward */ 1723 DEFAULT(csiescseq.arg[0], 1); 1724 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1725 break; 1726 case 'E': /* CNL -- Cursor <n> Down and first col */ 1727 DEFAULT(csiescseq.arg[0], 1); 1728 tmoveto(0, term.c.y+csiescseq.arg[0]); 1729 break; 1730 case 'F': /* CPL -- Cursor <n> Up and first col */ 1731 DEFAULT(csiescseq.arg[0], 1); 1732 tmoveto(0, term.c.y-csiescseq.arg[0]); 1733 break; 1734 case 'g': /* TBC -- Tabulation clear */ 1735 switch (csiescseq.arg[0]) { 1736 case 0: /* clear current tab stop */ 1737 term.tabs[term.c.x] = 0; 1738 break; 1739 case 3: /* clear all the tabs */ 1740 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1741 break; 1742 default: 1743 goto unknown; 1744 } 1745 break; 1746 case 'G': /* CHA -- Move to <col> */ 1747 case '`': /* HPA */ 1748 DEFAULT(csiescseq.arg[0], 1); 1749 tmoveto(csiescseq.arg[0]-1, term.c.y); 1750 break; 1751 case 'H': /* CUP -- Move to <row> <col> */ 1752 case 'f': /* HVP */ 1753 DEFAULT(csiescseq.arg[0], 1); 1754 DEFAULT(csiescseq.arg[1], 1); 1755 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1756 break; 1757 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1758 DEFAULT(csiescseq.arg[0], 1); 1759 tputtab(csiescseq.arg[0]); 1760 break; 1761 case 'J': /* ED -- Clear screen */ 1762 switch (csiescseq.arg[0]) { 1763 case 0: /* below */ 1764 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1765 if (term.c.y < term.row-1) { 1766 tclearregion(0, term.c.y+1, term.col-1, 1767 term.row-1); 1768 } 1769 break; 1770 case 1: /* above */ 1771 if (term.c.y > 1) 1772 tclearregion(0, 0, term.col-1, term.c.y-1); 1773 tclearregion(0, term.c.y, term.c.x, term.c.y); 1774 break; 1775 case 2: /* all */ 1776 tclearregion(0, 0, term.col-1, term.row-1); 1777 break; 1778 default: 1779 goto unknown; 1780 } 1781 break; 1782 case 'K': /* EL -- Clear line */ 1783 switch (csiescseq.arg[0]) { 1784 case 0: /* right */ 1785 tclearregion(term.c.x, term.c.y, term.col-1, 1786 term.c.y); 1787 break; 1788 case 1: /* left */ 1789 tclearregion(0, term.c.y, term.c.x, term.c.y); 1790 break; 1791 case 2: /* all */ 1792 tclearregion(0, term.c.y, term.col-1, term.c.y); 1793 break; 1794 } 1795 break; 1796 case 'S': /* SU -- Scroll <n> line up */ 1797 DEFAULT(csiescseq.arg[0], 1); 1798 tscrollup(term.top, csiescseq.arg[0], 0); 1799 break; 1800 case 'T': /* SD -- Scroll <n> line down */ 1801 DEFAULT(csiescseq.arg[0], 1); 1802 tscrolldown(term.top, csiescseq.arg[0], 0); 1803 break; 1804 case 'L': /* IL -- Insert <n> blank lines */ 1805 DEFAULT(csiescseq.arg[0], 1); 1806 tinsertblankline(csiescseq.arg[0]); 1807 break; 1808 case 'l': /* RM -- Reset Mode */ 1809 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1810 break; 1811 case 'M': /* DL -- Delete <n> lines */ 1812 DEFAULT(csiescseq.arg[0], 1); 1813 tdeleteline(csiescseq.arg[0]); 1814 break; 1815 case 'X': /* ECH -- Erase <n> char */ 1816 DEFAULT(csiescseq.arg[0], 1); 1817 tclearregion(term.c.x, term.c.y, 1818 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1819 break; 1820 case 'P': /* DCH -- Delete <n> char */ 1821 DEFAULT(csiescseq.arg[0], 1); 1822 tdeletechar(csiescseq.arg[0]); 1823 break; 1824 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 1825 DEFAULT(csiescseq.arg[0], 1); 1826 tputtab(-csiescseq.arg[0]); 1827 break; 1828 case 'd': /* VPA -- Move to <row> */ 1829 DEFAULT(csiescseq.arg[0], 1); 1830 tmoveato(term.c.x, csiescseq.arg[0]-1); 1831 break; 1832 case 'h': /* SM -- Set terminal mode */ 1833 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1834 break; 1835 case 'm': /* SGR -- Terminal attribute (color) */ 1836 tsetattr(csiescseq.arg, csiescseq.narg); 1837 break; 1838 case 'n': /* DSR – Device Status Report (cursor position) */ 1839 if (csiescseq.arg[0] == 6) { 1840 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1841 term.c.y+1, term.c.x+1); 1842 ttywrite(buf, len, 0); 1843 } 1844 break; 1845 case 'r': /* DECSTBM -- Set Scrolling Region */ 1846 if (csiescseq.priv) { 1847 goto unknown; 1848 } else { 1849 DEFAULT(csiescseq.arg[0], 1); 1850 DEFAULT(csiescseq.arg[1], term.row); 1851 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1852 tmoveato(0, 0); 1853 } 1854 break; 1855 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1856 tcursor(CURSOR_SAVE); 1857 break; 1858 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1859 tcursor(CURSOR_LOAD); 1860 break; 1861 case ' ': 1862 switch (csiescseq.mode[1]) { 1863 case 'q': /* DECSCUSR -- Set Cursor Style */ 1864 if (xsetcursor(csiescseq.arg[0])) 1865 goto unknown; 1866 break; 1867 default: 1868 goto unknown; 1869 } 1870 break; 1871 } 1872 } 1873 1874 void 1875 csidump(void) 1876 { 1877 size_t i; 1878 uint c; 1879 1880 fprintf(stderr, "ESC["); 1881 for (i = 0; i < csiescseq.len; i++) { 1882 c = csiescseq.buf[i] & 0xff; 1883 if (isprint(c)) { 1884 putc(c, stderr); 1885 } else if (c == '\n') { 1886 fprintf(stderr, "(\\n)"); 1887 } else if (c == '\r') { 1888 fprintf(stderr, "(\\r)"); 1889 } else if (c == 0x1b) { 1890 fprintf(stderr, "(\\e)"); 1891 } else { 1892 fprintf(stderr, "(%02x)", c); 1893 } 1894 } 1895 putc('\n', stderr); 1896 } 1897 1898 void 1899 csireset(void) 1900 { 1901 memset(&csiescseq, 0, sizeof(csiescseq)); 1902 } 1903 1904 void 1905 strhandle(void) 1906 { 1907 char *p = NULL, *dec; 1908 int j, narg, par; 1909 1910 term.esc &= ~(ESC_STR_END|ESC_STR); 1911 strparse(); 1912 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1913 1914 switch (strescseq.type) { 1915 case ']': /* OSC -- Operating System Command */ 1916 switch (par) { 1917 case 0: 1918 case 1: 1919 case 2: 1920 if (narg > 1) 1921 xsettitle(strescseq.args[1]); 1922 return; 1923 case 52: 1924 if (narg > 2 && allowwindowops) { 1925 dec = base64dec(strescseq.args[2]); 1926 if (dec) { 1927 xsetsel(dec); 1928 xclipcopy(); 1929 } else { 1930 fprintf(stderr, "erresc: invalid base64\n"); 1931 } 1932 } 1933 return; 1934 case 4: /* color set */ 1935 if (narg < 3) 1936 break; 1937 p = strescseq.args[2]; 1938 /* FALLTHROUGH */ 1939 case 104: /* color reset, here p = NULL */ 1940 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1941 if (xsetcolorname(j, p)) { 1942 if (par == 104 && narg <= 1) 1943 return; /* color reset without parameter */ 1944 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1945 j, p ? p : "(null)"); 1946 } else { 1947 /* 1948 * TODO if defaultbg color is changed, borders 1949 * are dirty 1950 */ 1951 redraw(); 1952 } 1953 return; 1954 } 1955 break; 1956 case 'k': /* old title set compatibility */ 1957 xsettitle(strescseq.args[0]); 1958 return; 1959 case 'P': /* DCS -- Device Control String */ 1960 case '_': /* APC -- Application Program Command */ 1961 case '^': /* PM -- Privacy Message */ 1962 return; 1963 } 1964 1965 fprintf(stderr, "erresc: unknown str "); 1966 strdump(); 1967 } 1968 1969 void 1970 strparse(void) 1971 { 1972 int c; 1973 char *p = strescseq.buf; 1974 1975 strescseq.narg = 0; 1976 strescseq.buf[strescseq.len] = '\0'; 1977 1978 if (*p == '\0') 1979 return; 1980 1981 while (strescseq.narg < STR_ARG_SIZ) { 1982 strescseq.args[strescseq.narg++] = p; 1983 while ((c = *p) != ';' && c != '\0') 1984 ++p; 1985 if (c == '\0') 1986 return; 1987 *p++ = '\0'; 1988 } 1989 } 1990 1991 void 1992 strdump(void) 1993 { 1994 size_t i; 1995 uint c; 1996 1997 fprintf(stderr, "ESC%c", strescseq.type); 1998 for (i = 0; i < strescseq.len; i++) { 1999 c = strescseq.buf[i] & 0xff; 2000 if (c == '\0') { 2001 putc('\n', stderr); 2002 return; 2003 } else if (isprint(c)) { 2004 putc(c, stderr); 2005 } else if (c == '\n') { 2006 fprintf(stderr, "(\\n)"); 2007 } else if (c == '\r') { 2008 fprintf(stderr, "(\\r)"); 2009 } else if (c == 0x1b) { 2010 fprintf(stderr, "(\\e)"); 2011 } else { 2012 fprintf(stderr, "(%02x)", c); 2013 } 2014 } 2015 fprintf(stderr, "ESC\\\n"); 2016 } 2017 2018 void 2019 strreset(void) 2020 { 2021 strescseq = (STREscape){ 2022 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2023 .siz = STR_BUF_SIZ, 2024 }; 2025 } 2026 2027 void 2028 sendbreak(const Arg *arg) 2029 { 2030 if (tcsendbreak(cmdfd, 0)) 2031 perror("Error sending break"); 2032 } 2033 2034 void 2035 tprinter(char *s, size_t len) 2036 { 2037 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2038 perror("Error writing to output file"); 2039 close(iofd); 2040 iofd = -1; 2041 } 2042 } 2043 2044 void 2045 toggleprinter(const Arg *arg) 2046 { 2047 term.mode ^= MODE_PRINT; 2048 } 2049 2050 void 2051 printscreen(const Arg *arg) 2052 { 2053 tdump(); 2054 } 2055 2056 void 2057 printsel(const Arg *arg) 2058 { 2059 tdumpsel(); 2060 } 2061 2062 void 2063 tdumpsel(void) 2064 { 2065 char *ptr; 2066 2067 if ((ptr = getsel())) { 2068 tprinter(ptr, strlen(ptr)); 2069 free(ptr); 2070 } 2071 } 2072 2073 void 2074 tdumpline(int n) 2075 { 2076 char buf[UTF_SIZ]; 2077 Glyph *bp, *end; 2078 2079 bp = &term.line[n][0]; 2080 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2081 if (bp != end || bp->u != ' ') { 2082 for ( ; bp <= end; ++bp) 2083 tprinter(buf, utf8encode(bp->u, buf)); 2084 } 2085 tprinter("\n", 1); 2086 } 2087 2088 void 2089 tdump(void) 2090 { 2091 int i; 2092 2093 for (i = 0; i < term.row; ++i) 2094 tdumpline(i); 2095 } 2096 2097 void 2098 tputtab(int n) 2099 { 2100 uint x = term.c.x; 2101 2102 if (n > 0) { 2103 while (x < term.col && n--) 2104 for (++x; x < term.col && !term.tabs[x]; ++x) 2105 /* nothing */ ; 2106 } else if (n < 0) { 2107 while (x > 0 && n++) 2108 for (--x; x > 0 && !term.tabs[x]; --x) 2109 /* nothing */ ; 2110 } 2111 term.c.x = LIMIT(x, 0, term.col-1); 2112 } 2113 2114 void 2115 tdefutf8(char ascii) 2116 { 2117 if (ascii == 'G') 2118 term.mode |= MODE_UTF8; 2119 else if (ascii == '@') 2120 term.mode &= ~MODE_UTF8; 2121 } 2122 2123 void 2124 tdeftran(char ascii) 2125 { 2126 static char cs[] = "0B"; 2127 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2128 char *p; 2129 2130 if ((p = strchr(cs, ascii)) == NULL) { 2131 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2132 } else { 2133 term.trantbl[term.icharset] = vcs[p - cs]; 2134 } 2135 } 2136 2137 void 2138 tdectest(char c) 2139 { 2140 int x, y; 2141 2142 if (c == '8') { /* DEC screen alignment test. */ 2143 for (x = 0; x < term.col; ++x) { 2144 for (y = 0; y < term.row; ++y) 2145 tsetchar('E', &term.c.attr, x, y); 2146 } 2147 } 2148 } 2149 2150 void 2151 tstrsequence(uchar c) 2152 { 2153 switch (c) { 2154 case 0x90: /* DCS -- Device Control String */ 2155 c = 'P'; 2156 break; 2157 case 0x9f: /* APC -- Application Program Command */ 2158 c = '_'; 2159 break; 2160 case 0x9e: /* PM -- Privacy Message */ 2161 c = '^'; 2162 break; 2163 case 0x9d: /* OSC -- Operating System Command */ 2164 c = ']'; 2165 break; 2166 } 2167 strreset(); 2168 strescseq.type = c; 2169 term.esc |= ESC_STR; 2170 } 2171 2172 void 2173 tcontrolcode(uchar ascii) 2174 { 2175 switch (ascii) { 2176 case '\t': /* HT */ 2177 tputtab(1); 2178 return; 2179 case '\b': /* BS */ 2180 tmoveto(term.c.x-1, term.c.y); 2181 return; 2182 case '\r': /* CR */ 2183 tmoveto(0, term.c.y); 2184 return; 2185 case '\f': /* LF */ 2186 case '\v': /* VT */ 2187 case '\n': /* LF */ 2188 /* go to first col if the mode is set */ 2189 tnewline(IS_SET(MODE_CRLF)); 2190 return; 2191 case '\a': /* BEL */ 2192 if (term.esc & ESC_STR_END) { 2193 /* backwards compatibility to xterm */ 2194 strhandle(); 2195 } else { 2196 xbell(); 2197 } 2198 break; 2199 case '\033': /* ESC */ 2200 csireset(); 2201 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2202 term.esc |= ESC_START; 2203 return; 2204 case '\016': /* SO (LS1 -- Locking shift 1) */ 2205 case '\017': /* SI (LS0 -- Locking shift 0) */ 2206 term.charset = 1 - (ascii - '\016'); 2207 return; 2208 case '\032': /* SUB */ 2209 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2210 /* FALLTHROUGH */ 2211 case '\030': /* CAN */ 2212 csireset(); 2213 break; 2214 case '\005': /* ENQ (IGNORED) */ 2215 case '\000': /* NUL (IGNORED) */ 2216 case '\021': /* XON (IGNORED) */ 2217 case '\023': /* XOFF (IGNORED) */ 2218 case 0177: /* DEL (IGNORED) */ 2219 return; 2220 case 0x80: /* TODO: PAD */ 2221 case 0x81: /* TODO: HOP */ 2222 case 0x82: /* TODO: BPH */ 2223 case 0x83: /* TODO: NBH */ 2224 case 0x84: /* TODO: IND */ 2225 break; 2226 case 0x85: /* NEL -- Next line */ 2227 tnewline(1); /* always go to first col */ 2228 break; 2229 case 0x86: /* TODO: SSA */ 2230 case 0x87: /* TODO: ESA */ 2231 break; 2232 case 0x88: /* HTS -- Horizontal tab stop */ 2233 term.tabs[term.c.x] = 1; 2234 break; 2235 case 0x89: /* TODO: HTJ */ 2236 case 0x8a: /* TODO: VTS */ 2237 case 0x8b: /* TODO: PLD */ 2238 case 0x8c: /* TODO: PLU */ 2239 case 0x8d: /* TODO: RI */ 2240 case 0x8e: /* TODO: SS2 */ 2241 case 0x8f: /* TODO: SS3 */ 2242 case 0x91: /* TODO: PU1 */ 2243 case 0x92: /* TODO: PU2 */ 2244 case 0x93: /* TODO: STS */ 2245 case 0x94: /* TODO: CCH */ 2246 case 0x95: /* TODO: MW */ 2247 case 0x96: /* TODO: SPA */ 2248 case 0x97: /* TODO: EPA */ 2249 case 0x98: /* TODO: SOS */ 2250 case 0x99: /* TODO: SGCI */ 2251 break; 2252 case 0x9a: /* DECID -- Identify Terminal */ 2253 ttywrite(vtiden, strlen(vtiden), 0); 2254 break; 2255 case 0x9b: /* TODO: CSI */ 2256 case 0x9c: /* TODO: ST */ 2257 break; 2258 case 0x90: /* DCS -- Device Control String */ 2259 case 0x9d: /* OSC -- Operating System Command */ 2260 case 0x9e: /* PM -- Privacy Message */ 2261 case 0x9f: /* APC -- Application Program Command */ 2262 tstrsequence(ascii); 2263 return; 2264 } 2265 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2266 term.esc &= ~(ESC_STR_END|ESC_STR); 2267 } 2268 2269 /* 2270 * returns 1 when the sequence is finished and it hasn't to read 2271 * more characters for this sequence, otherwise 0 2272 */ 2273 int 2274 eschandle(uchar ascii) 2275 { 2276 switch (ascii) { 2277 case '[': 2278 term.esc |= ESC_CSI; 2279 return 0; 2280 case '#': 2281 term.esc |= ESC_TEST; 2282 return 0; 2283 case '%': 2284 term.esc |= ESC_UTF8; 2285 return 0; 2286 case 'P': /* DCS -- Device Control String */ 2287 case '_': /* APC -- Application Program Command */ 2288 case '^': /* PM -- Privacy Message */ 2289 case ']': /* OSC -- Operating System Command */ 2290 case 'k': /* old title set compatibility */ 2291 tstrsequence(ascii); 2292 return 0; 2293 case 'n': /* LS2 -- Locking shift 2 */ 2294 case 'o': /* LS3 -- Locking shift 3 */ 2295 term.charset = 2 + (ascii - 'n'); 2296 break; 2297 case '(': /* GZD4 -- set primary charset G0 */ 2298 case ')': /* G1D4 -- set secondary charset G1 */ 2299 case '*': /* G2D4 -- set tertiary charset G2 */ 2300 case '+': /* G3D4 -- set quaternary charset G3 */ 2301 term.icharset = ascii - '('; 2302 term.esc |= ESC_ALTCHARSET; 2303 return 0; 2304 case 'D': /* IND -- Linefeed */ 2305 if (term.c.y == term.bot) { 2306 tscrollup(term.top, 1, 1); 2307 } else { 2308 tmoveto(term.c.x, term.c.y+1); 2309 } 2310 break; 2311 case 'E': /* NEL -- Next line */ 2312 tnewline(1); /* always go to first col */ 2313 break; 2314 case 'H': /* HTS -- Horizontal tab stop */ 2315 term.tabs[term.c.x] = 1; 2316 break; 2317 case 'M': /* RI -- Reverse index */ 2318 if (term.c.y == term.top) { 2319 tscrolldown(term.top, 1, 1); 2320 } else { 2321 tmoveto(term.c.x, term.c.y-1); 2322 } 2323 break; 2324 case 'Z': /* DECID -- Identify Terminal */ 2325 ttywrite(vtiden, strlen(vtiden), 0); 2326 break; 2327 case 'c': /* RIS -- Reset to initial state */ 2328 treset(); 2329 resettitle(); 2330 xloadcols(); 2331 break; 2332 case '=': /* DECPAM -- Application keypad */ 2333 xsetmode(1, MODE_APPKEYPAD); 2334 break; 2335 case '>': /* DECPNM -- Normal keypad */ 2336 xsetmode(0, MODE_APPKEYPAD); 2337 break; 2338 case '7': /* DECSC -- Save Cursor */ 2339 tcursor(CURSOR_SAVE); 2340 break; 2341 case '8': /* DECRC -- Restore Cursor */ 2342 tcursor(CURSOR_LOAD); 2343 break; 2344 case '\\': /* ST -- String Terminator */ 2345 if (term.esc & ESC_STR_END) 2346 strhandle(); 2347 break; 2348 default: 2349 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2350 (uchar) ascii, isprint(ascii)? ascii:'.'); 2351 break; 2352 } 2353 return 1; 2354 } 2355 2356 void 2357 tputc(Rune u) 2358 { 2359 char c[UTF_SIZ]; 2360 int control; 2361 int width, len; 2362 Glyph *gp; 2363 2364 control = ISCONTROL(u); 2365 if (u < 127 || !IS_SET(MODE_UTF8)) { 2366 c[0] = u; 2367 width = len = 1; 2368 } else { 2369 len = utf8encode(u, c); 2370 if (!control && (width = wcwidth(u)) == -1) 2371 width = 1; 2372 } 2373 2374 if (IS_SET(MODE_PRINT)) 2375 tprinter(c, len); 2376 2377 /* 2378 * STR sequence must be checked before anything else 2379 * because it uses all following characters until it 2380 * receives a ESC, a SUB, a ST or any other C1 control 2381 * character. 2382 */ 2383 if (term.esc & ESC_STR) { 2384 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2385 ISCONTROLC1(u)) { 2386 term.esc &= ~(ESC_START|ESC_STR); 2387 term.esc |= ESC_STR_END; 2388 goto check_control_code; 2389 } 2390 2391 if (strescseq.len+len >= strescseq.siz) { 2392 /* 2393 * Here is a bug in terminals. If the user never sends 2394 * some code to stop the str or esc command, then st 2395 * will stop responding. But this is better than 2396 * silently failing with unknown characters. At least 2397 * then users will report back. 2398 * 2399 * In the case users ever get fixed, here is the code: 2400 */ 2401 /* 2402 * term.esc = 0; 2403 * strhandle(); 2404 */ 2405 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2406 return; 2407 strescseq.siz *= 2; 2408 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2409 } 2410 2411 memmove(&strescseq.buf[strescseq.len], c, len); 2412 strescseq.len += len; 2413 return; 2414 } 2415 2416 check_control_code: 2417 /* 2418 * Actions of control codes must be performed as soon they arrive 2419 * because they can be embedded inside a control sequence, and 2420 * they must not cause conflicts with sequences. 2421 */ 2422 if (control) { 2423 tcontrolcode(u); 2424 /* 2425 * control codes are not shown ever 2426 */ 2427 if (!term.esc) 2428 term.lastc = 0; 2429 return; 2430 } else if (term.esc & ESC_START) { 2431 if (term.esc & ESC_CSI) { 2432 csiescseq.buf[csiescseq.len++] = u; 2433 if (BETWEEN(u, 0x40, 0x7E) 2434 || csiescseq.len >= \ 2435 sizeof(csiescseq.buf)-1) { 2436 term.esc = 0; 2437 csiparse(); 2438 csihandle(); 2439 } 2440 return; 2441 } else if (term.esc & ESC_UTF8) { 2442 tdefutf8(u); 2443 } else if (term.esc & ESC_ALTCHARSET) { 2444 tdeftran(u); 2445 } else if (term.esc & ESC_TEST) { 2446 tdectest(u); 2447 } else { 2448 if (!eschandle(u)) 2449 return; 2450 /* sequence already finished */ 2451 } 2452 term.esc = 0; 2453 /* 2454 * All characters which form part of a sequence are not 2455 * printed 2456 */ 2457 return; 2458 } 2459 if (selected(term.c.x, term.c.y)) 2460 selclear(); 2461 2462 gp = &term.line[term.c.y][term.c.x]; 2463 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2464 gp->mode |= ATTR_WRAP; 2465 tnewline(1); 2466 gp = &term.line[term.c.y][term.c.x]; 2467 } 2468 2469 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2470 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2471 2472 if (term.c.x+width > term.col) { 2473 tnewline(1); 2474 gp = &term.line[term.c.y][term.c.x]; 2475 } 2476 2477 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2478 term.lastc = u; 2479 2480 if (width == 2) { 2481 gp->mode |= ATTR_WIDE; 2482 if (term.c.x+1 < term.col) { 2483 gp[1].u = '\0'; 2484 gp[1].mode = ATTR_WDUMMY; 2485 } 2486 } 2487 if (term.c.x+width < term.col) { 2488 tmoveto(term.c.x+width, term.c.y); 2489 } else { 2490 term.c.state |= CURSOR_WRAPNEXT; 2491 } 2492 } 2493 2494 int 2495 twrite(const char *buf, int buflen, int show_ctrl) 2496 { 2497 int charsize; 2498 Rune u; 2499 int n; 2500 2501 for (n = 0; n < buflen; n += charsize) { 2502 if (IS_SET(MODE_UTF8)) { 2503 /* process a complete utf8 char */ 2504 charsize = utf8decode(buf + n, &u, buflen - n); 2505 if (charsize == 0) 2506 break; 2507 } else { 2508 u = buf[n] & 0xFF; 2509 charsize = 1; 2510 } 2511 if (show_ctrl && ISCONTROL(u)) { 2512 if (u & 0x80) { 2513 u &= 0x7f; 2514 tputc('^'); 2515 tputc('['); 2516 } else if (u != '\n' && u != '\r' && u != '\t') { 2517 u ^= 0x40; 2518 tputc('^'); 2519 } 2520 } 2521 tputc(u); 2522 } 2523 return n; 2524 } 2525 2526 void 2527 tresize(int col, int row) 2528 { 2529 int i, j; 2530 int minrow = MIN(row, term.row); 2531 int mincol = MIN(col, term.col); 2532 int *bp; 2533 TCursor c; 2534 2535 if (col < 1 || row < 1) { 2536 fprintf(stderr, 2537 "tresize: error resizing to %dx%d\n", col, row); 2538 return; 2539 } 2540 2541 /* 2542 * slide screen to keep cursor where we expect it - 2543 * tscrollup would work here, but we can optimize to 2544 * memmove because we're freeing the earlier lines 2545 */ 2546 for (i = 0; i <= term.c.y - row; i++) { 2547 free(term.line[i]); 2548 free(term.alt[i]); 2549 } 2550 /* ensure that both src and dst are not NULL */ 2551 if (i > 0) { 2552 memmove(term.line, term.line + i, row * sizeof(Line)); 2553 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2554 } 2555 for (i += row; i < term.row; i++) { 2556 free(term.line[i]); 2557 free(term.alt[i]); 2558 } 2559 2560 /* resize to new height */ 2561 term.line = xrealloc(term.line, row * sizeof(Line)); 2562 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2563 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2564 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2565 2566 for (i = 0; i < HISTSIZE; i++) { 2567 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2568 for (j = mincol; j < col; j++) { 2569 term.hist[i][j] = term.c.attr; 2570 term.hist[i][j].u = ' '; 2571 } 2572 } 2573 2574 /* resize each row to new width, zero-pad if needed */ 2575 for (i = 0; i < minrow; i++) { 2576 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2577 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2578 } 2579 2580 /* allocate any new rows */ 2581 for (/* i = minrow */; i < row; i++) { 2582 term.line[i] = xmalloc(col * sizeof(Glyph)); 2583 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2584 } 2585 if (col > term.col) { 2586 bp = term.tabs + term.col; 2587 2588 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2589 while (--bp > term.tabs && !*bp) 2590 /* nothing */ ; 2591 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2592 *bp = 1; 2593 } 2594 /* update terminal size */ 2595 term.col = col; 2596 term.row = row; 2597 /* reset scrolling region */ 2598 tsetscroll(0, row-1); 2599 /* make use of the LIMIT in tmoveto */ 2600 tmoveto(term.c.x, term.c.y); 2601 /* Clearing both screens (it makes dirty all lines) */ 2602 c = term.c; 2603 for (i = 0; i < 2; i++) { 2604 if (mincol < col && 0 < minrow) { 2605 tclearregion(mincol, 0, col - 1, minrow - 1); 2606 } 2607 if (0 < col && minrow < row) { 2608 tclearregion(0, minrow, col - 1, row - 1); 2609 } 2610 tswapscreen(); 2611 tcursor(CURSOR_LOAD); 2612 } 2613 term.c = c; 2614 } 2615 2616 void 2617 resettitle(void) 2618 { 2619 xsettitle(NULL); 2620 } 2621 2622 void 2623 drawregion(int x1, int y1, int x2, int y2) 2624 { 2625 int y; 2626 2627 for (y = y1; y < y2; y++) { 2628 if (!term.dirty[y]) 2629 continue; 2630 2631 term.dirty[y] = 0; 2632 xdrawline(TLINE(y), x1, y, x2); 2633 } 2634 } 2635 2636 void 2637 draw(void) 2638 { 2639 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2640 2641 if (!xstartdraw()) 2642 return; 2643 2644 /* adjust cursor position */ 2645 LIMIT(term.ocx, 0, term.col-1); 2646 LIMIT(term.ocy, 0, term.row-1); 2647 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2648 term.ocx--; 2649 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2650 cx--; 2651 2652 drawregion(0, 0, term.col, term.row); 2653 if (term.scr == 0) 2654 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2655 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2656 term.ocx = cx; 2657 term.ocy = term.c.y; 2658 xfinishdraw(); 2659 if (ocx != term.ocx || ocy != term.ocy) 2660 xximspot(term.ocx, term.ocy); 2661 } 2662 2663 void 2664 redraw(void) 2665 { 2666 tfulldirt(); 2667 draw(); 2668 }