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