mtm.c
1 #include <errno.h> 2 #include <fcntl.h> 3 #include <limits.h> 4 #include <locale.h> 5 #include <pwd.h> 6 #include <signal.h> 7 #include <stdbool.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <sys/ioctl.h> 11 #include <sys/select.h> 12 #include <sys/types.h> 13 #include <unistd.h> 14 #include <wchar.h> 15 #include <wctype.h> 16 17 #include "vtparser.h" 18 19 /*** CONFIGURATION */ 20 #include "config.h" 21 22 #define MIN(x, y) ((x) < (y)? (x) : (y)) 23 #define MAX(x, y) ((x) > (y)? (x) : (y)) 24 #define CTL(x) ((x) & 0x1f) 25 #define USAGE "usage: mtm [-T NAME] [-t NAME] [-c KEY]\n" 26 27 /*** DATA TYPES */ 28 typedef enum{ 29 HORIZONTAL, 30 VERTICAL, 31 VIEW 32 } Node; 33 34 typedef struct SCRN SCRN; 35 struct SCRN{ 36 int sy, sx, vis, tos; 37 short fg, bg, sfg, sbg, sp; 38 bool insert, oxenl, xenl, saved; 39 attr_t sattr; 40 WINDOW *win; 41 }; 42 43 typedef struct NODE NODE; 44 struct NODE{ 45 Node t; 46 int y, x, h, w, pt, ntabs; 47 bool *tabs, pnm, decom, am, lnm; 48 wchar_t repc; 49 NODE *p, *c1, *c2; 50 SCRN pri, alt, *s; 51 wchar_t *g0, *g1, *g2, *g3, *gc, *gs, *sgc, *sgs; 52 VTPARSER vp; 53 }; 54 55 /*** GLOBALS AND PROTOTYPES */ 56 static NODE *root, *focused, *lastfocused = NULL; 57 static int commandkey = CTL(COMMAND_KEY), nfds = 1; /* stdin */ 58 static fd_set fds; 59 static char iobuf[BUFSIZ]; 60 61 static void setupevents(NODE *n); 62 static void reshape(NODE *n, int y, int x, int h, int w); 63 static void draw(NODE *n); 64 static void reshapechildren(NODE *n); 65 static const char *term = NULL; 66 static void freenode(NODE *n, bool recursive); 67 void start_pairs(void); 68 short mtm_alloc_pair(int fg, int bg); 69 70 /*** UTILITY FUNCTIONS */ 71 static void 72 quit(int rc, const char *m) /* Shut down MTM. */ 73 { 74 if (m) 75 fprintf(stderr, "%s\n", m); 76 if (root) 77 freenode(root, true); 78 endwin(); 79 exit(rc); 80 } 81 82 static void 83 safewrite(int fd, const char *b, size_t n) /* Write, checking for errors. */ 84 { 85 size_t w = 0; 86 while (w < n) { 87 ssize_t s = write(fd, b + w, n - w); 88 if (s < 0 && errno != EINTR) 89 return; 90 if (s < 0) 91 s = 0; 92 w += (size_t)s; 93 } 94 } 95 96 static const char * 97 getshell(void) /* Get the user's preferred shell. */ 98 { 99 if (getenv("SHELL")) 100 return getenv("SHELL"); 101 struct passwd *pwd = getpwuid(getuid()); 102 if (pwd) 103 return pwd->pw_shell; 104 return "/bin/sh"; 105 } 106 107 /*** TERMINAL EMULATION HANDLERS 108 * These functions implement the various terminal commands activated by 109 * escape sequences and printing to the terminal. Large amounts of boilerplate 110 * code is shared among all these functions, and is factored out into the 111 * macros below: 112 * PD(n, d): Parameter n, with default d. 113 * P0(n): Parameter n, default 0. 114 * P1(n): Parameter n, default 1. 115 * CALL(h): Call handler h with no arguments. 116 * SENDN(n, s, c): Write string c bytes of s to n. 117 * SEND(n, s): Write string s to node n's host. 118 * (END)HANDLER: Declare/end a handler function 119 * COMMONVARS: All of the common variables for a handler. 120 * x, y: cursor position 121 * mx, my: max possible values for x and y 122 * px, py: physical cursor position in scrollback 123 * n: the current node 124 * win: the current window 125 * top, bot: the scrolling region 126 * tos: top of the screen in the pad 127 * s: the current SCRN buffer 128 * The funny names for handlers are from their ANSI/ECMA/DEC mnemonics. 129 */ 130 #define PD(x, d) (argc < (x) || !argv? (d) : argv[(x)]) 131 #define P0(x) PD(x, 0) 132 #define P1(x) (!P0(x)? 1 : P0(x)) 133 #define CALL(x) (x)(v, n, 0, 0, 0, NULL, NULL) 134 #define SENDN(n, s, c) safewrite(n->pt, s, c) 135 #define SEND(n, s) SENDN(n, s, strlen(s)) 136 #define COMMONVARS \ 137 NODE *n = (NODE *)p; \ 138 SCRN *s = n->s; \ 139 WINDOW *win = s->win; \ 140 int py, px, y, x, my, mx, top = 0, bot = 0, tos = s->tos; \ 141 (void)v; (void)p; (void)w; (void)iw; (void)argc; (void)argv; \ 142 (void)win; (void)y; (void)x; (void)my; (void)mx; (void)osc; \ 143 (void)tos; \ 144 getyx(win, py, px); y = py - s->tos; x = px; \ 145 getmaxyx(win, my, mx); my -= s->tos; \ 146 wgetscrreg(win, &top, &bot); \ 147 bot++; bot -= s->tos; \ 148 top = top <= tos? 0 : top - tos; \ 149 150 #define HANDLER(name) \ 151 static void \ 152 name (VTPARSER *v, void *p, wchar_t w, wchar_t iw, \ 153 int argc, int *argv, const wchar_t *osc) \ 154 { COMMONVARS 155 #define ENDHANDLER n->repc = 0; } /* control sequences aren't repeated */ 156 157 HANDLER(bell) /* Terminal bell. */ 158 beep(); 159 ENDHANDLER 160 161 HANDLER(numkp) /* Application/Numeric Keypad Mode */ 162 n->pnm = (w == L'='); 163 ENDHANDLER 164 165 HANDLER(vis) /* Cursor visibility */ 166 s->vis = iw == L'6'? 0 : 1; 167 ENDHANDLER 168 169 HANDLER(cup) /* CUP - Cursor Position */ 170 s->xenl = false; 171 wmove(win, tos + (n->decom? top : 0) + P1(0) - 1, P1(1) - 1); 172 ENDHANDLER 173 174 HANDLER(dch) /* DCH - Delete Character */ 175 for (int i = 0; i < P1(0); i++) 176 wdelch(win); 177 ENDHANDLER 178 179 HANDLER(ich) /* ICH - Insert Character */ 180 for (int i = 0; i < P1(0); i++) 181 wins_nwstr(win, L" ", 1); 182 ENDHANDLER 183 184 HANDLER(cuu) /* CUU - Cursor Up */ 185 wmove(win, MAX(py - P1(0), tos + top), x); 186 ENDHANDLER 187 188 HANDLER(cud) /* CUD - Cursor Down */ 189 wmove(win, MIN(py + P1(0), tos + bot - 1), x); 190 ENDHANDLER 191 192 HANDLER(cuf) /* CUF - Cursor Forward */ 193 wmove(win, py, MIN(x + P1(0), mx - 1)); 194 ENDHANDLER 195 196 HANDLER(ack) /* ACK - Acknowledge Enquiry */ 197 SEND(n, "\006"); 198 ENDHANDLER 199 200 HANDLER(hts) /* HTS - Horizontal Tab Set */ 201 if (x < n->ntabs && x > 0) 202 n->tabs[x] = true; 203 ENDHANDLER 204 205 HANDLER(ri) /* RI - Reverse Index */ 206 int otop = 0, obot = 0; 207 wgetscrreg(win, &otop, &obot); 208 wsetscrreg(win, otop >= tos? otop : tos, obot); 209 y == top? wscrl(win, -1) : wmove(win, MAX(tos, py - 1), x); 210 wsetscrreg(win, otop, obot); 211 ENDHANDLER 212 213 HANDLER(decid) /* DECID - Send Terminal Identification */ 214 if (w == L'c') 215 SEND(n, iw == L'>'? "\033[>1;10;0c" : "\033[?1;2c"); 216 else if (w == L'Z') 217 SEND(n, "\033[?6c"); 218 ENDHANDLER 219 220 HANDLER(hpa) /* HPA - Cursor Horizontal Absolute */ 221 wmove(win, py, MIN(P1(0) - 1, mx - 1)); 222 ENDHANDLER 223 224 HANDLER(hpr) /* HPR - Cursor Horizontal Relative */ 225 wmove(win, py, MIN(px + P1(0), mx - 1)); 226 ENDHANDLER 227 228 HANDLER(vpa) /* VPA - Cursor Vertical Absolute */ 229 wmove(win, MIN(tos + bot - 1, MAX(tos + top, tos + P1(0) - 1)), x); 230 ENDHANDLER 231 232 HANDLER(vpr) /* VPR - Cursor Vertical Relative */ 233 wmove(win, MIN(tos + bot - 1, MAX(tos + top, py + P1(0))), x); 234 ENDHANDLER 235 236 HANDLER(cbt) /* CBT - Cursor Backwards Tab */ 237 for (int i = x - 1; i < n->ntabs && i >= 0; i--) if (n->tabs[i]){ 238 wmove(win, py, i); 239 return; 240 } 241 wmove(win, py, 0); 242 ENDHANDLER 243 244 HANDLER(ht) /* HT - Horizontal Tab */ 245 for (int i = x + 1; i < n->w && i < n->ntabs; i++) if (n->tabs[i]){ 246 wmove(win, py, i); 247 return; 248 } 249 wmove(win, py, mx - 1); 250 ENDHANDLER 251 252 HANDLER(tab) /* Tab forwards or backwards */ 253 for (int i = 0; i < P1(0); i++) 254 switch (w) { 255 case L'I': CALL(ht); break; 256 case L'\t': CALL(ht); break; 257 case L'Z': CALL(cbt); break; 258 } 259 ENDHANDLER 260 261 HANDLER(decaln) /* DECALN - Screen Alignment Test */ 262 chtype e[] = {COLOR_PAIR(0) | 'E', 0}; 263 for (int r = 0; r < my; r++) 264 for (int c = 0; c <= mx; c++) 265 mvwaddchnstr(win, tos + r, c, e, 1); 266 wmove(win, py, px); 267 ENDHANDLER 268 269 HANDLER(su) /* SU - Scroll Up/Down */ 270 wscrl(win, (w == L'T' || w == L'^')? -P1(0) : P1(0)); 271 ENDHANDLER 272 273 HANDLER(sc) /* SC - Save Cursor */ 274 s->sx = px; /* save X position */ 275 s->sy = py; /* save Y position */ 276 wattr_get(win, &s->sattr, &s->sp, NULL); /* save attrs and color pair */ 277 s->sfg = s->fg; /* save foreground color */ 278 s->sbg = s->bg; /* save background color */ 279 s->oxenl = s->xenl; /* save xenl state */ 280 s->saved = true; /* save data is valid */ 281 n->sgc = n->gc; n->sgs = n->gs; /* save character sets */ 282 ENDHANDLER 283 284 HANDLER(rc) /* RC - Restore Cursor */ 285 if (iw == L'#'){ 286 CALL(decaln); 287 return; 288 } 289 290 if (!s->saved) 291 return; 292 293 wmove(win, s->sy, s->sx); /* get old position */ 294 wattr_set(win, s->sattr, s->sp, NULL); /* get attrs and color pair */ 295 s->fg = s->sfg; /* get foreground color */ 296 s->bg = s->sbg; /* get background color */ 297 s->xenl = s->oxenl; /* get xenl state */ 298 n->gc = n->sgc; n->gs = n->sgs; /* save character sets */ 299 300 /* restore colors */ 301 int cp = mtm_alloc_pair(s->fg, s->bg); 302 wcolor_set(win, cp, NULL); 303 cchar_t c; 304 setcchar(&c, L" ", A_NORMAL, cp, NULL); 305 wbkgrndset(win, &c); 306 ENDHANDLER 307 308 HANDLER(tbc) /* TBC - Tabulation Clear */ 309 switch (P0(0)) { 310 case 0: n->tabs[x < n->ntabs? x : 0] = false; break; 311 case 3: memset(n->tabs, 0, sizeof(bool) * (n->ntabs)); break; 312 } 313 ENDHANDLER 314 315 HANDLER(cub) /* CUB - Cursor Backward */ 316 s->xenl = false; 317 wmove(win, py, MAX(x - P1(0), 0)); 318 ENDHANDLER 319 320 HANDLER(el) /* EL - Erase in Line */ 321 cchar_t b; 322 setcchar(&b, L" ", A_NORMAL, mtm_alloc_pair(s->fg, s->bg), NULL); 323 324 switch (P0(0)) { 325 case 0: wclrtoeol(win); break; 326 case 1: 327 for (int i = 0; i <= x; i++) 328 mvwadd_wchnstr(win, py, i, &b, 1); break; 329 case 2: wmove(win, py, 0); wclrtoeol(win); break; 330 } 331 332 wmove(win, py, x); 333 ENDHANDLER 334 335 HANDLER(ed) /* ED - Erase in Display */ 336 int o = 1; 337 338 switch (P0(0)){ 339 case 0: wclrtobot(win); break; 340 case 3: werase(win); break; 341 case 2: wmove(win, tos, 0); wclrtobot(win); break; 342 case 1: 343 for (int i = tos; i < py; i++){ 344 wmove(win, i, 0); 345 wclrtoeol(win); 346 } 347 wmove(win, py, x); 348 el(v, p, w, iw, 1, &o, NULL); 349 break; 350 } 351 352 wmove(win, py, px); 353 ENDHANDLER 354 355 HANDLER(ech) /* ECH - Erase Character */ 356 cchar_t c; 357 setcchar(&c, L" ", A_NORMAL, mtm_alloc_pair(s->fg, s->bg), NULL); 358 for (int i = 0; i < P1(0); i++) 359 mvwadd_wchnstr(win, py, x + i, &c, 1); 360 wmove(win, py, px); 361 ENDHANDLER 362 363 HANDLER(dsr) /* DSR - Device Status Report */ 364 char buf[100] = {0}; 365 if (P0(0) == 6) 366 snprintf( 367 buf, 368 sizeof(buf) - 1, 369 "\033[%d;%dR", 370 (n->decom? y - top : y) + 1, 371 x + 1 372 ); 373 else 374 snprintf(buf, sizeof(buf) - 1, "\033[0n"); 375 SEND(n, buf); 376 ENDHANDLER 377 378 HANDLER(idl) /* IL or DL - Insert/Delete Line */ 379 /* we don't use insdelln here because it inserts above and not below, 380 * and has a few other edge cases... */ 381 int otop = 0, obot = 0, p1 = MIN(P1(0), (my - 1) - y); 382 wgetscrreg(win, &otop, &obot); 383 wsetscrreg(win, py, obot); 384 wscrl(win, w == L'L'? -p1 : p1); 385 wsetscrreg(win, otop, obot); 386 wmove(win, py, 0); 387 ENDHANDLER 388 389 HANDLER(csr) /* CSR - Change Scrolling Region */ 390 if (wsetscrreg(win, tos + P1(0) - 1, tos + PD(1, my) - 1) == OK) 391 CALL(cup); 392 ENDHANDLER 393 394 HANDLER(decreqtparm) /* DECREQTPARM - Request Device Parameters */ 395 SEND(n, P0(0)? "\033[3;1;2;120;1;0x" : "\033[2;1;2;120;128;1;0x"); 396 ENDHANDLER 397 398 HANDLER(sgr0) /* Reset SGR to default */ 399 wattrset(win, A_NORMAL); 400 wcolor_set(win, 0, NULL); 401 s->fg = s->bg = -1; 402 wbkgdset(win, COLOR_PAIR(0) | ' '); 403 ENDHANDLER 404 405 HANDLER(cls) /* Clear screen */ 406 CALL(cup); 407 wclrtobot(win); 408 CALL(cup); 409 ENDHANDLER 410 411 HANDLER(ris) /* RIS - Reset to Initial State */ 412 n->gs = n->gc = n->g0 = CSET_US; n->g1 = CSET_GRAPH; 413 n->g2 = CSET_US; n->g3 = CSET_GRAPH; 414 n->decom = s->insert = s->oxenl = s->xenl = n->lnm = false; 415 CALL(cls); 416 CALL(sgr0); 417 n->am = true; 418 n->pnm = false; 419 n->pri.vis = n->alt.vis = 1; 420 n->s = &n->pri; 421 wsetscrreg(n->pri.win, 0, MAX(SCROLLBACK, n->h) - 1); 422 wsetscrreg(n->alt.win, 0, n->h - 1); 423 for (int i = 0; i < n->ntabs; i++) 424 n->tabs[i] = (i % 8 == 0); 425 ENDHANDLER 426 427 HANDLER(mode) /* Set or Reset Mode */ 428 bool set = (w == L'h'); 429 for (int i = 0; i < argc; i++) 430 switch (P0(i)) { 431 case 1: n->pnm = set; break; 432 case 3: CALL(cls); break; 433 case 4: s->insert = set; break; 434 case 6: n->decom = set; CALL(cup); break; 435 case 7: n->am = set; break; 436 case 20: n->lnm = set; break; 437 case 25: s->vis = set? 1 : 0; break; 438 case 34: s->vis = set? 1 : 2; break; 439 case 1048: CALL((set? sc : rc)); break; 440 case 1049: 441 CALL((set? sc : rc)); /* fall-through */ 442 case 47: 443 case 1047: 444 if (set && n->s != &n->alt) { 445 n->s = &n->alt; 446 CALL(cls); 447 } else if (!set && n->s != &n->pri) { 448 n->s = &n->pri; 449 } 450 break; 451 } 452 ENDHANDLER 453 454 HANDLER(sgr) /* SGR - Select Graphic Rendition */ 455 bool doc = false; 456 bool do8 = COLORS >= 8; 457 bool do16 = COLORS >= 16; 458 bool do256 = COLORS >= 256; 459 460 if (!argc) 461 CALL(sgr0); 462 463 short bg = s->bg, fg = s->fg; 464 for (int i = 0; i < argc; i++) { 465 switch (P0(i)) { 466 case 0: CALL(sgr0); break; 467 case 1: wattron(win, A_BOLD); break; 468 case 2: wattron(win, A_DIM); break; 469 case 4: wattron(win, A_UNDERLINE); break; 470 case 5: wattron(win, A_BLINK); break; 471 case 7: wattron(win, A_REVERSE); break; 472 case 8: wattron(win, A_INVIS); break; 473 case 22: wattroff(win, A_DIM); wattroff(win, A_BOLD); break; 474 case 24: wattroff(win, A_UNDERLINE); break; 475 case 25: wattroff(win, A_BLINK); break; 476 case 27: wattroff(win, A_REVERSE); break; 477 case 30: fg = COLOR_BLACK; doc = do8; break; 478 case 31: fg = COLOR_RED; doc = do8; break; 479 case 32: fg = COLOR_GREEN; doc = do8; break; 480 case 33: fg = COLOR_YELLOW; doc = do8; break; 481 case 34: fg = COLOR_BLUE; doc = do8; break; 482 case 35: fg = COLOR_MAGENTA; doc = do8; break; 483 case 36: fg = COLOR_CYAN; doc = do8; break; 484 case 37: fg = COLOR_WHITE; doc = do8; break; 485 case 38: fg = P0(i+1) == 5? P0(i+2) : s->fg; i += 2; doc = do256; break; 486 case 39: fg = -1; doc = true; break; 487 case 40: bg = COLOR_BLACK; doc = do8; break; 488 case 41: bg = COLOR_RED; doc = do8; break; 489 case 42: bg = COLOR_GREEN; doc = do8; break; 490 case 43: bg = COLOR_YELLOW; doc = do8; break; 491 case 44: bg = COLOR_BLUE; doc = do8; break; 492 case 45: bg = COLOR_MAGENTA; doc = do8; break; 493 case 46: bg = COLOR_CYAN; doc = do8; break; 494 case 47: bg = COLOR_WHITE; doc = do8; break; 495 case 48: bg = P0(i+1) == 5? P0(i+2) : s->bg; i += 2; doc = do256; break; 496 case 49: bg = -1; doc = true; break; 497 case 90: fg = COLOR_BLACK; doc = do16; break; 498 case 91: fg = COLOR_RED; doc = do16; break; 499 case 92: fg = COLOR_GREEN; doc = do16; break; 500 case 93: fg = COLOR_YELLOW; doc = do16; break; 501 case 94: fg = COLOR_BLUE; doc = do16; break; 502 case 95: fg = COLOR_MAGENTA; doc = do16; break; 503 case 96: fg = COLOR_CYAN; doc = do16; break; 504 case 97: fg = COLOR_WHITE; doc = do16; break; 505 case 100: bg = COLOR_BLACK; doc = do16; break; 506 case 101: bg = COLOR_RED; doc = do16; break; 507 case 102: bg = COLOR_GREEN; doc = do16; break; 508 case 103: bg = COLOR_YELLOW; doc = do16; break; 509 case 104: bg = COLOR_BLUE; doc = do16; break; 510 case 105: bg = COLOR_MAGENTA; doc = do16; break; 511 case 106: bg = COLOR_CYAN; doc = do16; break; 512 case 107: bg = COLOR_WHITE; doc = do16; break; 513 514 #if defined(A_ITALIC) && !defined(NO_ITALICS) && !defined(REVERSE_ITALICS) 515 case 3: wattron(win, A_ITALIC); break; 516 case 23: wattroff(win, A_ITALIC); break; 517 #endif 518 519 #if defined(REVERSE_ITALICS) 520 case 3: wattron(win, A_REVERSE); break; 521 case 23: wattroff(win, A_REVERSE); break; 522 #endif 523 } 524 } 525 526 if (doc) { 527 int p = mtm_alloc_pair(s->fg = fg, s->bg = bg); 528 wcolor_set(win, p, NULL); 529 cchar_t c; 530 setcchar(&c, L" ", A_NORMAL, p, NULL); 531 wbkgrndset(win, &c); 532 } 533 } 534 535 HANDLER(cr) /* CR - Carriage Return */ 536 s->xenl = false; 537 wmove(win, py, 0); 538 ENDHANDLER 539 540 HANDLER(ind) /* IND - Index */ 541 y == (bot - 1)? scroll(win) : wmove(win, py + 1, x); 542 ENDHANDLER 543 544 HANDLER(nel) /* NEL - Next Line */ 545 CALL(cr); CALL(ind); 546 ENDHANDLER 547 548 HANDLER(pnl) /* NL - Newline */ 549 CALL((n->lnm? nel : ind)); 550 ENDHANDLER 551 552 HANDLER(cpl) /* CPL - Cursor Previous Line */ 553 wmove(win, MAX(tos + top, py - P1(0)), 0); 554 ENDHANDLER 555 556 HANDLER(cnl) /* CNL - Cursor Next Line */ 557 wmove(win, MIN(tos + bot - 1, py + P1(0)), 0); 558 ENDHANDLER 559 560 HANDLER(print) /* Print a character to the terminal */ 561 if (wcwidth(w) < 0) 562 return; 563 564 if (s->insert) 565 CALL(ich); 566 567 if (s->xenl) { 568 s->xenl = false; 569 if (n->am) 570 CALL(nel); 571 getyx(win, y, x); 572 y -= tos; 573 } 574 575 if (w < MAXMAP && n->gc[w]) 576 w = n->gc[w]; 577 n->repc = w; 578 579 if (x == mx - wcwidth(w)) { 580 s->xenl = true; 581 wins_nwstr(win, &w, 1); 582 } else { 583 waddnwstr(win, &w, 1); 584 } 585 n->gc = n->gs; 586 } /* no ENDHANDLER because we don't want to reset repc */ 587 588 HANDLER(rep) /* REP - Repeat Character */ 589 for (int i = 0; i < P1(0) && n->repc; i++) 590 print(v, p, n->repc, 0, 0, NULL, NULL); 591 ENDHANDLER 592 593 HANDLER(scs) /* Select Character Set */ 594 wchar_t **t = NULL; 595 596 switch (iw){ 597 case L'(': t = &n->g0; break; 598 case L')': t = &n->g1; break; 599 case L'*': t = &n->g2; break; 600 case L'+': t = &n->g3; break; 601 default: return; 602 } 603 604 switch (w){ 605 case L'A': *t = CSET_UK; break; 606 case L'B': *t = CSET_US; break; 607 case L'0': *t = CSET_GRAPH; break; 608 case L'1': *t = CSET_US; break; 609 case L'2': *t = CSET_GRAPH; break; 610 } 611 ENDHANDLER 612 613 HANDLER(so) /* Switch Out/In Character Set */ 614 if (w == 0x0e) { 615 n->gs = n->gc = n->g1; /* locking shift */ 616 } else if (w == 0xf) { 617 n->gs = n->gc = n->g0; /* locking shift */ 618 } else if (w == L'n') { 619 n->gs = n->gc = n->g2; /* locking shift */ 620 } else if (w == L'o') { 621 n->gs = n->gc = n->g3; /* locking shift */ 622 } else if (w == L'N') { 623 n->gs = n->gc; /* non-locking shift */ 624 n->gc = n->g2; 625 } else if (w == L'O'){ 626 n->gs = n->gc; /* non-locking shift */ 627 n->gc = n->g3; 628 } 629 ENDHANDLER 630 631 static void 632 setupevents(NODE *n) 633 { 634 n->vp.p = n; 635 vtonevent(&n->vp, VTPARSER_CONTROL, 0x05, ack); 636 vtonevent(&n->vp, VTPARSER_CONTROL, 0x07, bell); 637 vtonevent(&n->vp, VTPARSER_CONTROL, 0x08, cub); 638 vtonevent(&n->vp, VTPARSER_CONTROL, 0x09, tab); 639 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0a, pnl); 640 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0b, pnl); 641 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0c, pnl); 642 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0d, cr); 643 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0e, so); 644 vtonevent(&n->vp, VTPARSER_CONTROL, 0x0f, so); 645 vtonevent(&n->vp, VTPARSER_CSI, L'A', cuu); 646 vtonevent(&n->vp, VTPARSER_CSI, L'B', cud); 647 vtonevent(&n->vp, VTPARSER_CSI, L'C', cuf); 648 vtonevent(&n->vp, VTPARSER_CSI, L'D', cub); 649 vtonevent(&n->vp, VTPARSER_CSI, L'E', cnl); 650 vtonevent(&n->vp, VTPARSER_CSI, L'F', cpl); 651 vtonevent(&n->vp, VTPARSER_CSI, L'G', hpa); 652 vtonevent(&n->vp, VTPARSER_CSI, L'H', cup); 653 vtonevent(&n->vp, VTPARSER_CSI, L'I', tab); 654 vtonevent(&n->vp, VTPARSER_CSI, L'J', ed); 655 vtonevent(&n->vp, VTPARSER_CSI, L'K', el); 656 vtonevent(&n->vp, VTPARSER_CSI, L'L', idl); 657 vtonevent(&n->vp, VTPARSER_CSI, L'M', idl); 658 vtonevent(&n->vp, VTPARSER_CSI, L'P', dch); 659 vtonevent(&n->vp, VTPARSER_CSI, L'S', su); 660 vtonevent(&n->vp, VTPARSER_CSI, L'T', su); 661 vtonevent(&n->vp, VTPARSER_CSI, L'X', ech); 662 vtonevent(&n->vp, VTPARSER_CSI, L'Z', tab); 663 vtonevent(&n->vp, VTPARSER_CSI, L'`', hpa); 664 vtonevent(&n->vp, VTPARSER_CSI, L'^', su); 665 vtonevent(&n->vp, VTPARSER_CSI, L'@', ich); 666 vtonevent(&n->vp, VTPARSER_CSI, L'a', hpr); 667 vtonevent(&n->vp, VTPARSER_CSI, L'b', rep); 668 vtonevent(&n->vp, VTPARSER_CSI, L'c', decid); 669 vtonevent(&n->vp, VTPARSER_CSI, L'd', vpa); 670 vtonevent(&n->vp, VTPARSER_CSI, L'e', vpr); 671 vtonevent(&n->vp, VTPARSER_CSI, L'f', cup); 672 vtonevent(&n->vp, VTPARSER_CSI, L'g', tbc); 673 vtonevent(&n->vp, VTPARSER_CSI, L'h', mode); 674 vtonevent(&n->vp, VTPARSER_CSI, L'l', mode); 675 vtonevent(&n->vp, VTPARSER_CSI, L'm', sgr); 676 vtonevent(&n->vp, VTPARSER_CSI, L'n', dsr); 677 vtonevent(&n->vp, VTPARSER_CSI, L'r', csr); 678 vtonevent(&n->vp, VTPARSER_CSI, L's', sc); 679 vtonevent(&n->vp, VTPARSER_CSI, L'u', rc); 680 vtonevent(&n->vp, VTPARSER_CSI, L'x', decreqtparm); 681 vtonevent(&n->vp, VTPARSER_ESCAPE, L'0', scs); 682 vtonevent(&n->vp, VTPARSER_ESCAPE, L'1', scs); 683 vtonevent(&n->vp, VTPARSER_ESCAPE, L'2', scs); 684 vtonevent(&n->vp, VTPARSER_ESCAPE, L'7', sc); 685 vtonevent(&n->vp, VTPARSER_ESCAPE, L'8', rc); 686 vtonevent(&n->vp, VTPARSER_ESCAPE, L'A', scs); 687 vtonevent(&n->vp, VTPARSER_ESCAPE, L'B', scs); 688 vtonevent(&n->vp, VTPARSER_ESCAPE, L'D', ind); 689 vtonevent(&n->vp, VTPARSER_ESCAPE, L'E', nel); 690 vtonevent(&n->vp, VTPARSER_ESCAPE, L'H', hts); 691 vtonevent(&n->vp, VTPARSER_ESCAPE, L'M', ri); 692 vtonevent(&n->vp, VTPARSER_ESCAPE, L'Z', decid); 693 vtonevent(&n->vp, VTPARSER_ESCAPE, L'c', ris); 694 vtonevent(&n->vp, VTPARSER_ESCAPE, L'p', vis); 695 vtonevent(&n->vp, VTPARSER_ESCAPE, L'=', numkp); 696 vtonevent(&n->vp, VTPARSER_ESCAPE, L'>', numkp); 697 vtonevent(&n->vp, VTPARSER_PRINT, 0, print); 698 } 699 700 /*** MTM FUNCTIONS 701 * These functions do the user-visible work of MTM: creating nodes in the 702 * tree, updating the display, and so on. 703 */ 704 static bool * 705 newtabs(int w, int ow, bool *oldtabs) /* Initialize default tabstops. */ 706 { 707 bool *tabs = calloc(w, sizeof(bool)); 708 if (!tabs) 709 return NULL; 710 for (int i = 0; i < w; i++) /* keep old overlapping tabs */ 711 tabs[i] = i < ow? oldtabs[i] : (i % 8 == 0); 712 return tabs; 713 } 714 715 static NODE * 716 newnode(Node t, NODE *p, int y, int x, int h, int w) /* Create a new node. */ 717 { 718 NODE *n = calloc(1, sizeof(NODE)); 719 bool *tabs = newtabs(w, 0, NULL); 720 if (!n || h < 2 || w < 2 || !tabs) 721 return free(n), free(tabs), NULL; 722 723 n->t = t; 724 n->pt = -1; 725 n->p = p; 726 n->y = y; 727 n->x = x; 728 n->h = h; 729 n->w = w; 730 n->tabs = tabs; 731 n->ntabs = w; 732 733 return n; 734 } 735 736 static void 737 freenode(NODE *n, bool recurse) /* Free a node. */ 738 { 739 if (!n) return; 740 741 if (lastfocused == n) 742 lastfocused = NULL; 743 if (n->pri.win) 744 delwin(n->pri.win); 745 if (n->alt.win) 746 delwin(n->alt.win); 747 if (recurse) 748 freenode(n->c1, true); 749 if (recurse) 750 freenode(n->c2, true); 751 if (n->pt >= 0) { 752 close(n->pt); 753 FD_CLR(n->pt, &fds); 754 } 755 free(n->tabs); 756 free(n); 757 } 758 759 static void 760 fixcursor(void) /* Move the terminal cursor to the active view. */ 761 { 762 if (!focused) return; 763 764 int y, x; 765 curs_set(focused->s->off != focused->s->tos ? 0 : focused->s->vis); 766 getyx(focused->s->win, y, x); 767 y = MIN(MAX(y, focused->s->tos), focused->s->tos + focused->h - 1); 768 wmove(focused->s->win, y, x); 769 } 770 771 static const char * 772 getterm(void) 773 { 774 const char *envterm = getenv("TERM"); 775 if (term) 776 return term; 777 if (envterm && COLORS >= 256 && !strstr(DEFAULT_TERMINAL, "-256color")) 778 return DEFAULT_256_COLOR_TERMINAL; 779 return DEFAULT_TERMINAL; 780 } 781 782 static NODE * 783 newview(NODE *p, int y, int x, int h, int w) /* Open a new view. */ 784 { 785 struct winsize ws = {.ws_row = h, .ws_col = w}; 786 NODE *n = newnode(VIEW, p, y, x, h, w); 787 if (!n) 788 return NULL; 789 790 SCRN *pri = &n->pri, *alt = &n->alt; 791 pri->win = newpad(MAX(h, SCROLLBACK), w); 792 alt->win = newpad(h, w); 793 if (!pri->win || !alt->win) 794 return freenode(n, false), NULL; 795 pri->tos = MAX(0, SCROLLBACK - h); 796 n->s = pri; 797 798 nodelay(pri->win, TRUE); nodelay(alt->win, TRUE); 799 scrollok(pri->win, TRUE); scrollok(alt->win, TRUE); 800 keypad(pri->win, TRUE); keypad(alt->win, TRUE); 801 802 setupevents(n); 803 ris(&n->vp, n, L'c', 0, 0, NULL, NULL); 804 805 pid_t pid = forkpty(&n->pt, NULL, NULL, &ws); 806 if (pid < 0) { 807 if (!p) 808 perror("forkpty"); 809 return freenode(n, false), NULL; 810 } else if (pid == 0) { 811 char buf[100] = {0}; 812 snprintf(buf, sizeof(buf) - 1, "%lu", (unsigned long)getppid()); 813 setsid(); 814 setenv("MTM", buf, 1); 815 setenv("TERM", getterm(), 1); 816 signal(SIGCHLD, SIG_DFL); 817 execl(getshell(), getshell(), NULL); 818 return NULL; 819 } 820 821 FD_SET(n->pt, &fds); 822 fcntl(n->pt, F_SETFL, O_NONBLOCK); 823 nfds = n->pt > nfds? n->pt : nfds; 824 return n; 825 } 826 827 static NODE * 828 newcontainer( 829 Node t, 830 NODE *p, 831 int y, int x, 832 int h, int w, 833 NODE *c1, 834 NODE *c2 835 ) { 836 /* Create a new container */ 837 NODE *n = newnode(t, p, y, x, h, w); 838 if (!n) return NULL; 839 840 n->c1 = c1; 841 n->c2 = c2; 842 c1->p = c2->p = n; 843 844 reshapechildren(n); 845 return n; 846 } 847 848 static void 849 focus(NODE *n) /* Focus a node. */ 850 { 851 if (!n) return; 852 853 if (n->t == VIEW) { 854 lastfocused = focused; 855 focused = n; 856 return; 857 } 858 859 focus(n->c1? n->c1 : n->c2); 860 } 861 862 #define ABOVE(n) n->y - 2, n->x + n->w / 2 863 #define BELOW(n) n->y + n->h + 2, n->x + n->w / 2 864 #define LEFT(n) n->y + n->h / 2, n->x - 2 865 #define RIGHT(n) n->y + n->h / 2, n->x + n->w + 2 866 867 static NODE * 868 findnode(NODE *n, int y, int x) /* Find the node enclosing y,x. */ 869 { 870 #define IN(n, y, x) ( \ 871 y >= n->y && y <= n->y + n->h && \ 872 x >= n->x && x <= n->x + n->w \ 873 ) 874 875 if (!IN(n, y, x)) return NULL; 876 877 if (n->c1 && IN(n->c1, y, x)) 878 return findnode(n->c1, y, x); 879 880 if (n->c2 && IN(n->c2, y, x)) 881 return findnode(n->c2, y, x); 882 883 return n; 884 } 885 886 static void 887 replacechild(NODE *n, NODE *c1, NODE *c2) /* Replace c1 of n with c2. */ 888 { 889 c2->p = n; 890 if (!n) { 891 root = c2; 892 reshape(c2, 0, 0, LINES, COLS); 893 } else if (n->c1 == c1) { 894 n->c1 = c2; 895 } else if (n->c2 == c1) { 896 n->c2 = c2; 897 } 898 899 n = n? n : root; 900 reshape(n, n->y, n->x, n->h, n->w); 901 draw(n); 902 } 903 904 static void 905 removechild(NODE *p, const NODE *c) /* Replace p with other child. */ 906 { 907 replacechild(p->p, p, c == p->c1? p->c2 : p->c1); 908 freenode(p, false); 909 } 910 911 static void 912 deletenode(NODE *n) /* Delete a node. */ 913 { 914 if (!n || !n->p) 915 quit(EXIT_SUCCESS, NULL); 916 if (n == focused) 917 focus(n->p->c1 == n? n->p->c2 : n->p->c1); 918 removechild(n->p, n); 919 freenode(n, true); 920 } 921 922 static void 923 reshapeview(NODE *node, int d, int ow) /* Reshape a view. */ 924 { 925 int oy, ox; 926 bool *tabs = newtabs(node->w, ow, node->tabs); 927 struct winsize ws = {.ws_row = node->h, .ws_col = node->w}; 928 929 if (tabs) { 930 free(node->tabs); 931 node->tabs = tabs; 932 node->ntabs = node->w; 933 } 934 935 getyx(node->s->win, oy, ox); 936 wresize(node->pri.win, MAX(node->h, SCROLLBACK), MAX(node->w, 2)); 937 wresize(node->alt.win, MAX(node->h, 2), MAX(node->w, 2)); 938 node->pri.tos = node->pri.off = MAX(0, SCROLLBACK - node->h); 939 node->alt.tos = node->alt.off = 0; 940 wsetscrreg(node->pri.win, 0, MAX(SCROLLBACK, node->h) - 1); 941 wsetscrreg(node->alt.win, 0, node->h - 1); 942 if (d > 0){ /* make sure the new top line syncs up after reshape */ 943 wmove(node->s->win, oy + d, ox); 944 wscrl(node->s->win, -d); 945 } 946 doupdate(); 947 refresh(); 948 ioctl(node->pt, TIOCSWINSZ, &ws); 949 } 950 951 static void 952 reshapechildren(NODE *n) /* Reshape all children of a view. */ 953 { 954 if (n->t == HORIZONTAL){ 955 int i = n->w % 2? 0 : 1; 956 reshape(n->c1, n->y, n->x, n->h, n->w / 2); 957 reshape(n->c2, n->y, n->x + n->w / 2 + 1, n->h, n->w / 2 - i); 958 } else if (n->t == VERTICAL){ 959 int i = n->h % 2? 0 : 1; 960 reshape(n->c1, n->y, n->x, n->h / 2, n->w); 961 reshape(n->c2, n->y + n->h / 2 + 1, n->x, n->h / 2 - i, n->w); 962 } 963 } 964 965 static void 966 reshape(NODE *n, int y, int x, int h, int w) /* Reshape a node. */ 967 { 968 if (n->y == y && n->x == x && n->h == h && n->w == w && n->t == VIEW) 969 return; 970 971 int d = n->h - h; 972 int ow = n->w; 973 n->y = y; 974 n->x = x; 975 n->h = MAX(h, 1); 976 n->w = MAX(w, 1); 977 978 if (n->t == VIEW) 979 reshapeview(n, d, ow); 980 else 981 reshapechildren(n); 982 draw(n); 983 } 984 985 static void 986 drawchildren(const NODE *n) /* Draw all children of n. */ 987 { 988 draw(n->c1); 989 if (n->t == HORIZONTAL) 990 mvvline(n->y, n->x + n->w / 2, ACS_VLINE, n->h); 991 else 992 mvhline(n->y + n->h / 2, n->x, ACS_HLINE, n->w); 993 wnoutrefresh(stdscr); 994 draw(n->c2); 995 } 996 997 static void 998 draw(NODE *n) /* Draw a node. */ 999 { 1000 if (n->t == VIEW) { 1001 pnoutrefresh( 1002 n->s->win, n->s->off, 0, n->y, n->x, 1003 n->y + n->h - 1, n->x + n->w - 1 1004 ); 1005 return; 1006 } 1007 1008 drawchildren(n); 1009 } 1010 1011 static void 1012 split(NODE *n, Node t) /* Split a node. */ 1013 { 1014 int nh = t == VERTICAL? (n->h - 1) / 2 : n->h; 1015 int nw = t == HORIZONTAL? (n->w) / 2 : n->w; 1016 1017 NODE *p = n->p; 1018 1019 NODE *v = newview(NULL, 0, 0, MAX(0, nh), MAX(0, nw)); 1020 if (!v) return; 1021 1022 NODE *c = newcontainer(t, n->p, n->y, n->x, n->h, n->w, n, v); 1023 if (!c){ 1024 freenode(v, false); 1025 return; 1026 } 1027 1028 replacechild(p, n, c); 1029 focus(v); 1030 draw(p? p : root); 1031 } 1032 1033 static bool 1034 getinput(NODE *n, fd_set *f) /* Recursively check all ptty's for input. */ 1035 { 1036 if (n && n->c1 && !getinput(n->c1, f)) 1037 return false; 1038 1039 if (n && n->c2 && !getinput(n->c2, f)) 1040 return false; 1041 1042 if (n && n->t == VIEW && n->pt > 0 && FD_ISSET(n->pt, f)){ 1043 ssize_t r = read(n->pt, iobuf, sizeof(iobuf)); 1044 if (r > 0) 1045 vtwrite(&n->vp, iobuf, r); 1046 if (r <= 0 && errno != EINTR && errno != EWOULDBLOCK) 1047 return deletenode(n), false; 1048 } 1049 1050 return true; 1051 } 1052 1053 static void 1054 sendarrow(const NODE *n, const char *k) 1055 { 1056 char buf[100] = {0}; 1057 snprintf(buf, sizeof(buf) - 1, "\033%s%s", n->pnm? "O" : "[", k); 1058 SEND(n, buf); 1059 } 1060 1061 static bool 1062 handlechar(int r, int k) /* Handle a single input character. */ 1063 { 1064 const char cmdstr[] = {commandkey, 0}; 1065 static bool cmd = false; 1066 NODE *n = focused; 1067 1068 #define KERR(i) (r == ERR && (i) == k) 1069 #define KEY(i) (r == OK && (i) == k) 1070 #define CODE(i) (r == KEY_CODE_YES && (i) == k) 1071 #define INSCR (n->s->tos != n->s->off) 1072 #define DO(s, t, a) \ 1073 if (s == cmd && (t)) { \ 1074 a ; cmd = false; return true; \ 1075 } 1076 1077 DO(cmd, KERR(k), return false) 1078 DO(cmd, CODE(KEY_RESIZE), reshape(root, 0, 0, LINES, COLS)) 1079 DO(false, KEY(commandkey), return cmd = true) 1080 DO(false, KEY(0), SENDN(n, "\000", 1)) 1081 DO(false, KEY(L'\n'), SEND(n, "\n")) 1082 DO(false, KEY(L'\r'), SEND(n, n->lnm? "\r\n" : "\r")) 1083 DO(false, CODE(KEY_ENTER), SEND(n, n->lnm? "\r\n" : "\r")) 1084 DO(false, CODE(KEY_UP), sendarrow(n, "A")); 1085 DO(false, CODE(KEY_DOWN), sendarrow(n, "B")); 1086 DO(false, CODE(KEY_RIGHT), sendarrow(n, "C")); 1087 DO(false, CODE(KEY_LEFT), sendarrow(n, "D")); 1088 DO(false, CODE(KEY_HOME), SEND(n, "\033[1~")) 1089 DO(false, CODE(KEY_END), SEND(n, "\033[4~")) 1090 DO(false, CODE(KEY_PPAGE), SEND(n, "\033[5~")) 1091 DO(false, CODE(KEY_NPAGE), SEND(n, "\033[6~")) 1092 DO(false, CODE(KEY_BACKSPACE), SEND(n, "\177")) 1093 DO(false, CODE(KEY_DC), SEND(n, "\033[3~")) 1094 DO(false, CODE(KEY_IC), SEND(n, "\033[2~")) 1095 DO(false, CODE(KEY_BTAB), SEND(n, "\033[Z")) 1096 DO(false, CODE(KEY_F(1)), SEND(n, "\033OP")) 1097 DO(false, CODE(KEY_F(2)), SEND(n, "\033OQ")) 1098 DO(false, CODE(KEY_F(3)), SEND(n, "\033OR")) 1099 DO(false, CODE(KEY_F(4)), SEND(n, "\033OS")) 1100 DO(false, CODE(KEY_F(5)), SEND(n, "\033[15~")) 1101 DO(false, CODE(KEY_F(6)), SEND(n, "\033[17~")) 1102 DO(false, CODE(KEY_F(7)), SEND(n, "\033[18~")) 1103 DO(false, CODE(KEY_F(8)), SEND(n, "\033[19~")) 1104 DO(false, CODE(KEY_F(9)), SEND(n, "\033[20~")) 1105 DO(false, CODE(KEY_F(10)), SEND(n, "\033[21~")) 1106 DO(false, CODE(KEY_F(11)), SEND(n, "\033[23~")) 1107 DO(false, CODE(KEY_F(12)), SEND(n, "\033[24~")) 1108 DO(true, MOVE_UP, focus(findnode(root, ABOVE(n)))) 1109 DO(true, MOVE_DOWN, focus(findnode(root, BELOW(n)))) 1110 DO(true, MOVE_LEFT, focus(findnode(root, LEFT(n)))) 1111 DO(true, MOVE_RIGHT, focus(findnode(root, RIGHT(n)))) 1112 DO(true, MOVE_OTHER, focus(lastfocused)) 1113 DO(true, HSPLIT, split(n, HORIZONTAL)) 1114 DO(true, VSPLIT, split(n, VERTICAL)) 1115 DO(true, DELETE_NODE, deletenode(n)) 1116 DO(true, BAILOUT, (void)1) 1117 DO(true, REDRAW, touchwin(stdscr); draw(root); redrawwin(stdscr)) 1118 DO(true, KEY(commandkey), SENDN(n, cmdstr, 1)); 1119 1120 char c[MB_LEN_MAX + 1] = {0}; 1121 1122 if (wctomb(c, k) > 0) { 1123 scrollbottom(n); 1124 SEND(n, c); 1125 } 1126 1127 return cmd = false, true; 1128 } 1129 1130 static void 1131 run(void) /* Run MTM. */ 1132 { 1133 while (root) { 1134 wint_t w = 0; 1135 fd_set sfds = fds; 1136 if (select(nfds + 1, &sfds, NULL, NULL, NULL) < 0) 1137 FD_ZERO(&sfds); 1138 1139 int r = wget_wch(focused->s->win, &w); 1140 while (handlechar(r, w)) 1141 r = wget_wch(focused->s->win, &w); 1142 getinput(root, &sfds); 1143 1144 draw(root); 1145 doupdate(); 1146 fixcursor(); 1147 draw(focused); 1148 doupdate(); 1149 } 1150 } 1151 1152 int 1153 main(int argc, char **argv) 1154 { 1155 FD_SET(STDIN_FILENO, &fds); 1156 setlocale(LC_ALL, ""); 1157 signal(SIGCHLD, SIG_IGN); /* automatically reap children */ 1158 1159 int c = 0; 1160 for (;;) { 1161 c = getopt(argc, argv, "c:T:t:"); 1162 if (c == -1) break; 1163 1164 switch (c) { 1165 case 'c': commandkey = CTL(optarg[0]); break; 1166 case 'T': setenv("TERM", optarg, 1); break; 1167 case 't': term = optarg; break; 1168 default: quit(EXIT_FAILURE, USAGE); break; 1169 } 1170 } 1171 1172 if (!initscr()) 1173 quit(EXIT_FAILURE, "could not initialize terminal"); 1174 1175 ESCDELAY = ESCAPE_TIME; 1176 raw(); 1177 noecho(); 1178 nonl(); 1179 intrflush(stdscr, FALSE); 1180 start_color(); 1181 use_default_colors(); 1182 start_pairs(); 1183 1184 root = newview(NULL, 0, 0, LINES, COLS); 1185 if (!root) 1186 quit(EXIT_FAILURE, "could not open root window"); 1187 1188 focus(root); 1189 draw(root); 1190 run(); 1191 1192 quit(EXIT_SUCCESS, NULL); 1193 1194 return EXIT_SUCCESS; /* not reached */ 1195 } 1196