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