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