/* See license for details */ #include #include #include #include #include #include #include #include "term.h" #if defined(__linux__) || defined(__CYGWIN__) # include #elif defined(__FreeBSD__) || defined(__DragonFly__) # include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include #endif #define IS_CONTROL(ch) !((ch) & 0xffffff60UL) typedef struct Vt Vt; struct Vt { Buffer buf[2]; /* normal & alternative screen buffer */ Buffer *buffer; /* currently active buffer (one of the above) */ Pen pen; /* default pen */ int pty; /* master side pty file descriptor */ pid_t pid; /* process id of the process running in this vt */ /* flags */ char title[256]; /* xterm style window title */ void *data; /* user supplied data */ uint seen_input : 1; uint insert : 1; uint escaped : 1; uint curshid : 1; uint curskeymode : 1; uint bell : 1; uint relposmode : 1; uint mousetrack : 1; uint graphmode : 1; uint savgraphmode : 1; bool charsets[2]; /* buffers and parsing state */ char rbuf[BUFSIZ]; char ebuf[BUFSIZ]; uint rlen, elen; int srow, scol; /* last known offset to display start row, start column */ }; /* forward declares */ void vt·noscroll(Vt *t); void vt·dirty(Vt *t); void vt·scroll(Vt *t, int rows); static void puttab(Vt *t, int count); static void process_nonprinting(Vt *t, rune r); static void sendcurs(Vt *t); /* globals */ static int isutf8; static char vtname[36]; static void cclamp(Vt *t) { Buffer *b = t->buffer; Row *lines = t->relposmode ? b->scroll.top : b->lines; int rows = t->relposmode ? b->scroll.bot - b->scroll.top : b->rows; if (b->crow < lines) b->crow = lines; if (b->crow >= lines + rows) b->crow = lines + rows - 1; if (b->ccol < 0) b->ccol = 0; if (b->ccol >= b->cols) b->ccol = b->cols - 1; } static void clinedown(Vt *t) { Buffer *b = t->buffer; zero(b->crow, b->cols, b->maxcols - b->cols); b->crow++; if (b->crow < b->scroll.bot) return; vt·noscroll(t); b->crow = b->scroll.bot - 1; bscroll(b, 1); zero(b->crow, 0, b->cols); } static void csave(Vt *t) { Buffer *b = t->buffer; b->scrow = b->crow - b->lines; b->sccol = b->ccol; } static void crestore(Vt *t) { Buffer *b = t->buffer; b->crow = b->lines + b->scrow; b->ccol = b->sccol; cclamp(t); } static void savepen(Vt *t) { Buffer *b = t->buffer; b->spen = b->pen; } static void loadpen(Vt *t) { Buffer *b = t->buffer; b->pen = b->spen; } static void new_escape_sequence(Vt *t) { t->escaped = true; t->elen = 0; t->ebuf[0] = '\0'; } static void cancel_escape_sequence(Vt *t) { t->escaped = false; t->elen = 0; t->ebuf[0] = '\0'; } static bool is_valid_csi_ender(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '@' || c == '`'); } /* interprets a 'set attribute' (SGR) CSI escape sequence */ static void interpret_csi_sgr(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; if (pcount == 0) { /* special case: reset attributes */ b->pen.state = PenNormal; b->pen.col.fg = b->pen.col.bg = -1; return; } for (int i = 0; i < pcount; i++) { switch (param[i]) { case 0: b->pen.state = PenNormal; b->pen.col.fg = b->pen.col.bg = -1; break; case 1: b->pen.state |= PenBold; break; case 2: b->pen.state |= PenDim; break; case 3: b->pen.state |= PenItalic; break; case 4: b->pen.state |= PenUnderline; break; case 5: b->pen.state |= PenBlink; break; case 7: b->pen.state |= PenReverse; break; case 8: b->pen.state |= PenInvis; break; case 22: b->pen.state &= ~(PenBold | PenDim); break; case 23: b->pen.state &= ~PenItalic; break; case 24: b->pen.state &= ~PenUnderline; break; case 25: b->pen.state &= ~PenBlink; break; case 27: b->pen.state &= ~PenReverse; break; case 28: b->pen.state &= ~PenInvis; break; case 30 ... 37: /* fg */ b->pen.col.fg = param[i] - 30; break; case 38: if ((i + 2) < pcount && param[i + 1] == 5) { b->pen.col.fg = param[i + 2]; i += 2; } break; case 39: b->pen.col.fg = -1; break; case 40 ... 47: /* bg */ b->pen.col.bg = param[i] - 40; break; case 48: if ((i + 2) < pcount && param[i + 1] == 5) { b->pen.col.bg = param[i + 2]; i += 2; } break; case 49: b->pen.col.bg = -1; break; case 90 ... 97: /* hi fg */ b->pen.col.fg = param[i] - 82; break; case 100 ... 107: /* hi bg */ b->pen.col.bg = param[i] - 92; break; default: break; } } } /* interprets an 'erase display' (ED) escape sequence */ static void interpret_csi_ed(Vt *t, int param[], int pcount) { Row *row, *start, *end; Buffer *b = t->buffer; savepen(t); b->pen.state = PenNormal; b->pen.col.fg = b->pen.col.bg = -1; if (pcount && param[0] == 2) { start = b->lines; end = b->lines + b->rows; } else if (pcount && param[0] == 1) { start = b->lines; end = b->crow; zero(b->crow, 0, b->ccol + 1); } else { zero(b->crow, b->ccol, b->cols - b->ccol); start = b->crow + 1; end = b->lines + b->rows; } for (row = start; row < end; row++) zero(row, 0, b->cols); loadpen(t); } /* interprets a 'move cursor' (CUP) escape sequence */ static void interpret_csi_cup(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; Row *lines = t->relposmode ? b->scroll.top : b->lines; if (pcount == 0) { b->crow = lines; b->ccol = 0; } else if (pcount == 1) { b->crow = lines + param[0] - 1; b->ccol = 0; } else { b->crow = lines + param[0] - 1; b->ccol = param[1] - 1; } cclamp(t); } /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, * CPL, CHA, HPR, VPA, VPR, HPA */ static void interpret_csi_c(Vt *t, char verb, int param[], int pcount) { Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; switch (verb) { case 'A': b->crow -= n; break; case 'B': case 'e': b->crow += n; break; case 'C': case 'a': b->ccol += n; break; case 'D': b->ccol -= n; break; case 'E': b->crow += n; b->ccol = 0; break; case 'F': b->crow -= n; b->ccol = 0; break; case 'G': case '`': b->ccol = n - 1; break; case 'd': b->crow = b->lines + n - 1; break; } cclamp(t); } /* Interpret the 'erase line' escape sequence */ static void interpret_csi_el(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; switch (pcount ? param[0] : 0) { case 1: zero(b->crow, 0, b->ccol + 1); break; case 2: zero(b->crow, 0, b->cols); break; default: zero(b->crow, b->ccol, b->cols - b->ccol); break; } } /* Interpret the 'insert blanks' sequence (ICH) */ static void interpret_csi_ich(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; Row *row = b->crow; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->ccol + n > b->cols) n = b->cols - b->ccol; for (int i = b->cols - 1; i >= b->ccol + n; i--) row->cells[i] = row->cells[i - n]; zero(row, b->ccol, n); } /* Interpret the 'delete chars' sequence (DCH) */ static void interpret_csi_dch(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; Row *row = b->crow; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->ccol + n > b->cols) n = b->cols - b->ccol; for (int i = b->ccol; i < b->cols - n; i++) row->cells[i] = row->cells[i + n]; zero(row, b->cols - n, n); } /* Interpret an 'insert line' sequence (IL) */ static void interpret_csi_il(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->crow + n >= b->scroll.bot) { for (Row *row = b->crow; row < b->scroll.bot; row++) zero(row, 0, b->cols); } else { roll(b->crow, b->scroll.bot, -n); for (Row *row = b->crow; row < b->crow + n; row++) zero(row, 0, b->cols); } } /* Interpret a 'delete line' sequence (DL) */ static void interpret_csi_dl(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->crow + n >= b->scroll.bot) { for (Row *row = b->crow; row < b->scroll.bot; row++) zero(row, 0, b->cols); } else { roll(b->crow, b->scroll.bot, n); for (Row *row = b->scroll.bot - n; row < b->scroll.bot; row++) zero(row, 0, b->cols); } } /* Interpret an 'erase characters' (ECH) sequence */ static void interpret_csi_ech(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->ccol + n > b->cols) n = b->cols - b->ccol; zero(b->crow, b->ccol, n); } /* Interpret a 'set scrolling region' (DECSTBM) sequence */ static void interpret_csi_decstbm(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; int new_top, new_bot; switch (pcount) { case 0: b->scroll.top = b->lines; b->scroll.bot = b->lines + b->rows; break; case 2: new_top = param[0] - 1; new_bot = param[1]; /* clamp to bounds */ if (new_top < 0) new_top = 0; if (new_top >= b->rows) new_top = b->rows - 1; if (new_bot < 0) new_bot = 0; if (new_bot >= b->rows) new_bot = b->rows; /* check for range validity */ if (new_top < new_bot) { b->scroll.top = b->lines + new_top; b->scroll.bot = b->lines + new_bot; } break; default: return; /* malformed */ } b->crow = b->scroll.top; b->ccol = 0; } static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set) { for (int i = 0; i < pcount; i++) { switch (param[i]) { case 4: /* insert/replace mode */ t->insert = set; break; } } } static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set) { for (int i = 0; i < pcount; i++) { switch (param[i]) { case 1: /* set application/normal cursor key mode (DECCKM) */ t->curskeymode = set; break; case 6: /* set origin to relative/absolute (DECOM) */ t->relposmode = set; break; case 25: /* make cursor visible/invisible (DECCM) */ t->curshid = !set; break; case 1049: /* combine 1047 + 1048 */ case 47: /* use alternate/normal screen buffer */ case 1047: if (!set) bclear(&t->buf[1]); t->buffer = set ? &t->buf[1] : &t->buf[0]; vt·dirty(t); if (param[i] != 1049) break; /* fall through */ case 1048: /* save/restore cursor */ if (set) csave(t); else crestore(t); break; case 1000: /* enable/disable normal mouse tracking */ t->mousetrack = set; break; } } } static void interpret_csi(Vt *t) { Buffer *b = t->buffer; int csiparam[16]; uint param_count = 0; const char *p = t->ebuf + 1; char verb = t->ebuf[t->elen - 1]; /* parse numeric parameters */ for (p += (t->ebuf[1] == '?'); *p; p++) { if (IS_CONTROL(*p)) { process_nonprinting(t, *p); } else if (*p == ';') { if (param_count >= arrlen(csiparam)) return; /* too long! */ csiparam[param_count++] = 0; } else if (isdigit((uchar)*p)) { if (param_count == 0) csiparam[param_count++] = 0; csiparam[param_count - 1] *= 10; csiparam[param_count - 1] += *p - '0'; } } if (t->ebuf[1] == '?') { switch (verb) { case 'h': case 'l': /* private set/reset mode */ interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h'); break; } return; } /* delegate handling depending on command character (verb) */ switch (verb) { case 'h': case 'l': /* set/reset mode */ interpret_csi_mode(t, csiparam, param_count, verb == 'h'); break; case 'm': /* set attribute */ interpret_csi_sgr(t, csiparam, param_count); break; case 'J': /* erase display */ interpret_csi_ed(t, csiparam, param_count); break; case 'H': case 'f': /* move cursor */ interpret_csi_cup(t, csiparam, param_count); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'e': case 'a': case 'd': case '`': /* relative move */ interpret_csi_c(t, verb, csiparam, param_count); break; case 'K': /* erase line */ interpret_csi_el(t, csiparam, param_count); break; case '@': /* insert characters */ interpret_csi_ich(t, csiparam, param_count); break; case 'P': /* delete characters */ interpret_csi_dch(t, csiparam, param_count); break; case 'L': /* insert lines */ interpret_csi_il(t, csiparam, param_count); break; case 'M': /* delete lines */ interpret_csi_dl(t, csiparam, param_count); break; case 'X': /* erase chars */ interpret_csi_ech(t, csiparam, param_count); break; case 'S': /* SU: scroll up */ vt·scroll(t, param_count ? -csiparam[0] : -1); break; case 'T': /* SD: scroll down */ vt·scroll(t, param_count ? csiparam[0] : 1); break; case 'Z': /* CBT: cursor backward tabulation */ puttab(t, param_count ? -csiparam[0] : -1); break; case 'g': /* TBC: tabulation clear */ switch (param_count ? csiparam[0] : 0) { case 0: b->tabs[b->ccol] = false; break; case 3: memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols); break; } break; case 'r': /* set scrolling region */ interpret_csi_decstbm(t, csiparam, param_count); break; case 's': /* save cursor location */ csave(t); break; case 'u': /* restore cursor location */ crestore(t); break; case 'n': /* query cursor location */ if (param_count == 1 && csiparam[0] == 6) sendcurs(t); break; default: break; } } /* Interpret an 'index' (IND) sequence */ static void interpret_csi_ind(Vt *t) { Buffer *b = t->buffer; if (b->crow < b->lines + b->rows - 1) b->crow++; } /* Interpret a 'reverse index' (RI) sequence */ static void interpret_csi_ri(Vt *t) { Buffer *b = t->buffer; if (b->crow > b->scroll.top) b->crow--; else { roll(b->scroll.top, b->scroll.bot, -1); zero(b->scroll.top, 0, b->cols); } } /* Interpret a 'next line' (NEL) sequence */ static void interpret_csi_nel(Vt *t) { Buffer *b = t->buffer; if (b->crow < b->lines + b->rows - 1) { b->crow++; b->ccol = 0; } } /* Interpret a 'select character set' (SCS) sequence */ static void interpret_csi_scs(Vt *t) { /* ESC ( sets G0, ESC ) sets G1 */ t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0'); t->graphmode = t->charsets[0]; } /* Interpret an 'operating system command' (OSC) sequence */ static void interpret_osc(Vt *t) { /* ESC ] command ; data BEL * ESC ] command ; data ESC \\ * Note that BEL or ESC \\ have already been replaced with NUL. */ char *data = nil; int command = strtoul(t->ebuf + 1, &data, 10); if (data && *data == ';') { switch (command) { case 0: /* icon name and window title */ case 2: /* window title */ // if (t->title_handler) // t->title_handler(t, data+1); break; case 1: /* icon name */ break; default: #ifndef NDEBUG fprintf(stderr, "unknown OSC command: %d\n", command); #endif break; } } } static void try_interpret_escape_seq(Vt *t) { char lastchar = t->ebuf[t->elen - 1]; if (!*t->ebuf) return; switch (*t->ebuf) { case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */ if (t->elen == 2) { if (lastchar == '8') { /* DECALN */ interpret_csi_ed(t, (int []){ 2 }, 1); goto handled; } goto cancel; } break; case '(': case ')': if (t->elen == 2) { interpret_csi_scs(t); goto handled; } break; case ']': /* OSC - operating system command */ if (lastchar == '\a' || (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) { t->elen -= lastchar == '\a' ? 1 : 2; t->ebuf[t->elen] = '\0'; interpret_osc(t); goto handled; } break; case '[': /* CSI - control sequence introducer */ if (is_valid_csi_ender(lastchar)) { interpret_csi(t); goto handled; } break; case '7': /* DECSC: save cursor and attributes */ savepen(t); csave(t); goto handled; case '8': /* DECRC: restore cursor and attributes */ loadpen(t); crestore(t); goto handled; case 'D': /* IND: index */ interpret_csi_ind(t); goto handled; case 'M': /* RI: reverse index */ interpret_csi_ri(t); goto handled; case 'E': /* NEL: next line */ interpret_csi_nel(t); goto handled; case 'H': /* HTS: horizontal tab set */ t->buffer->tabs[t->buffer->ccol] = true; goto handled; default: goto cancel; } if (t->elen + 1 >= sizeof(t->ebuf)) { cancel: #ifndef NDEBUG fprintf(stderr, "cancelled: \\033"); for (uint i = 0; i < t->elen; i++) { if (isprint(t->ebuf[i])) { fputc(t->ebuf[i], stderr); } else { fprintf(stderr, "\\%03o", t->ebuf[i]); } } fputc('\n', stderr); #endif handled: cancel_escape_sequence(t); } } static void puttab(Vt *t, int count) { Buffer *b = t->buffer; int direction = count >= 0 ? 1 : -1; for (int col = b->ccol + direction; count; col += direction) { if (col < 0) { b->ccol = 0; break; } if (col >= b->cols) { b->ccol = b->cols - 1; break; } if (b->tabs[col]) { b->ccol = col; count -= direction; } } } static void process_nonprinting(Vt *t, rune r) { Buffer *b = t->buffer; switch (r) { case '\e': /* ESC */ new_escape_sequence(t); break; case '\a': /* BEL */ // if (t->urgent_handler) // t->urgent_handler(t); break; case '\b': /* BS */ if (b->ccol > 0) b->ccol--; break; case '\t': /* HT */ puttab(t, 1); break; case '\r': /* CR */ b->ccol = 0; break; case '\v': /* VT */ case '\f': /* FF */ case '\n': /* LF */ clinedown(t); break; case '\016': /* SO: shift out, invoke the G1 character set */ t->graphmode = t->charsets[1]; break; case '\017': /* SI: shift in, invoke the G0 character set */ t->graphmode = t->charsets[0]; break; } } static void is_utf8_locale(void) { const char *cset = nl_langinfo(CODESET); if (!cset) cset = "ANSI_X3.4-1968"; isutf8 = !strcmp(cset, "UTF-8"); } static wchar_t get_vt100_graphic(char c) { static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~"; /* * 5f-7e standard vt100 * 40-5e rxvt extension for extra curses acs chars */ static uint16_t const vt100_utf8[62] = { 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f 0, 0, 0, 0, 0, 0, 0, 0, // 50-57 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e }; if (isutf8) return vt100_utf8[c - 0x41]; // else if (strchr(vt100_acs, c)) // return NCURSES_ACS(c); return '\0'; } static void putrune(Vt *t, rune r) { Cell blank; int width = 0; if (!t->seen_input) { t->seen_input = 1; kill(-t->pid, SIGWINCH); } if (t->escaped) { if (t->elen + 1 < sizeof(t->ebuf)) { t->ebuf[t->elen] = r; t->ebuf[++t->elen] = '\0'; try_interpret_escape_seq(t); } else { cancel_escape_sequence(t); } } else if (IS_CONTROL(r)) { process_nonprinting(t, r); } else { if (t->graphmode) { if (r >= 0x41 && r <= 0x7e) { wchar_t gc = get_vt100_graphic(r); if (gc) r = gc; } width = 1; } else if ((width = wcwidth(r)) < 1) { width = 1; } Buffer *b = t->buffer; blank = (Cell){ .r = L'\0', .pen = (Pen){ .state = b->pen.state, .col = b->pen.col }, }; if (width == 2 && b->ccol == b->cols - 1) { b->crow->cells[b->ccol++] = blank; b->crow->dirty = true; } if (b->ccol >= b->cols) { b->ccol = 0; clinedown(t); } if (t->insert) { Cell *src = b->crow->cells + b->ccol; Cell *dest = src + width; size_t len = b->cols - b->ccol - width; memmove(dest, src, len * sizeof *dest); } b->crow->cells[b->ccol] = blank; b->crow->cells[b->ccol++].r = r; b->crow->dirty = true; if (width == 2) b->crow->cells[b->ccol++] = blank; } } int vt·process(Vt *t) { int res; uint pos = 0; mbstate_t ps; memset(&ps, 0, sizeof(ps)); if (t->pty < 0) { errno = EINVAL; return -1; } res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen); if (res < 0) return -1; t->rlen += res; while (pos < t->rlen) { rune r; size_t len; // XXX: convert this to use utf8 functions len = (ssize_t)mbrtowc((wchar*)&r, t->rbuf + pos, t->rlen - pos, &ps); if (len == -2) { t->rlen -= pos; memmove(t->rbuf, t->rbuf + pos, t->rlen); return 0; } if (len == -1) { len = 1; r = t->rbuf[pos]; } pos += len ? len : 1; putrune(t, r); } t->rlen -= pos; memmove(t->rbuf, t->rbuf + pos, t->rlen); return 0; } /* size is the number of rows kept in the scrollback */ Vt * vt·make(int rows, int cols, int size) { if (rows <= 0 || cols <= 0) return nil; Vt *t = calloc(1, sizeof(Vt)); if (!t) return nil; t->pty = -1; t->pen = (Pen) { .state = PenNormal, .col = {-1, -1}, }; t->buffer = &t->buf[0]; if (!binit(&t->buf[0], rows, cols, size) || !binit(&t->buf[1], rows, cols, 0)) { free(t); return nil; } return t; } void vt·resize(Vt *t, int rows, int cols) { struct winsize ws = { .ws_row = rows, .ws_col = cols }; if (rows <= 0 || cols <= 0) return; vt·noscroll(t); bresize(&t->buf[0], rows, cols); bresize(&t->buf[1], rows, cols); cclamp(t); ioctl(t->pty, TIOCSWINSZ, &ws); kill(-t->pid, SIGWINCH); } void vt·free(Vt *t) { if (!t) return; bfree(&t->buf[0]); bfree(&t->buf[1]); close(t->pty); free(t); } void vt·dirty(Vt *t) { Buffer *b = t->buffer; for (Row *row = b->lines, *end = row + b->rows; row < end; row++) row->dirty = true; } void vt·draw(Vt *t, Window *win, int srow, int scol) { int i, j; Cell *cell, *prev; Buffer *b = t->buffer; if (srow != t->srow || scol != t->scol) { vt·dirty(t); t->srow = srow; t->scol = scol; } for (i = 0; i < b->rows; i++) { Row *row = b->lines + i; if (!row->dirty) continue; wmove(win, srow + i, scol); for (j = 0; j < b->cols; j++) { prev = cell; cell = row->cells + j; if (!prev || !peneq(cell->pen, prev->pen)) { if (cell->pen.state == PenNormal) cell->pen.state = t->pen.state; if (cell->pen.col.fg == -1) cell->pen.col.fg = t->pen.col.fg; if (cell->pen.col.bg == -1) cell->pen.col.bg = t->pen.col.bg; // wattrset(win, cell->attr << NCURSES_ATTR_SHIFT); // wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), nil); } if (isutf8 && cell->r >= RuneSync) { char buf[MB_CUR_MAX + 1]; size_t len = wcrtomb(buf, cell->r, nil); if (len > 0) { waddnstr(win, buf, len); if (wcwidth(cell->r) > 1) j++; } } else waddch(win, cell->r > ' ' ? cell->r: ' '); } int x, y; getyx(win, y, x); (void)y; if (x && x < b->cols - 1) whline(win, ' ', b->cols - x); row->dirty = false; } wmove(win, srow + b->crow - b->lines, scol + b->ccol); } void vt·scroll(Vt *t, int rows) { Buffer *b = t->buffer; if (!b->scroll.size) return; if (rows < 0) { /* scroll back */ if (rows < -b->scroll.above) rows = -b->scroll.above; } else { /* scroll forward */ if (rows > b->scroll.below) rows = b->scroll.below; } bscroll(b, rows); b->scroll.below -= rows; } void vt·noscroll(Vt *t) { int below = t->buffer->scroll.below; if (below) vt·scroll(t, below); } pid_t vt·forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from) { int vt2ed[2], ed2vt[2]; struct winsize ws; ws.ws_row = t->buffer->rows; ws.ws_col = t->buffer->cols; ws.ws_xpixel = ws.ws_ypixel = 0; if (to && pipe(vt2ed)) { *to = -1; to = nil; } if (from && pipe(ed2vt)) { *from = -1; from = nil; } pid_t pid = forkpty(&t->pty, nil, nil, &ws); if (pid < 0) return -1; if (pid == 0) { setsid(); sigset_t emptyset; sigemptyset(&emptyset); sigprocmask(SIG_SETMASK, &emptyset, nil); if (to) { close(vt2ed[1]); dup2(vt2ed[0], STDIN_FILENO); close(vt2ed[0]); } if (from) { close(ed2vt[0]); dup2(ed2vt[1], STDOUT_FILENO); close(ed2vt[1]); } int maxfd = sysconf(_SC_OPEN_MAX); for (int fd = 3; fd < maxfd; fd++) if (close(fd) == -1 && errno == EBADF) break; for (const char **envp = env; envp && envp[0]; envp += 2) setenv(envp[0], envp[1], 1); setenv("TERM", vtname, 1); if (cwd) chdir(cwd); execvp(p, (char *const *)argv); fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]); exit(1); } if (to) { close(vt2ed[0]); *to = vt2ed[1]; } if (from) { close(ed2vt[1]); *from = ed2vt[0]; } return t->pid = pid; } int vt_pty_get(Vt *t) { return t->pty; } uintptr vt·write(Vt *t, const char *buf, size_t len) { uintptr res, ret = len; while (len > 0) { res = write(t->pty, buf, len); if (res < 0) { if (errno != EAGAIN && errno != EINTR) return -1; continue; } buf += res; len -= res; } return ret; } static void sendcurs(Vt *t) { Buffer *b = t->buffer; char keyseq[16]; snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->crow - b->lines), b->ccol); vt·write(t, keyseq, strlen(keyseq)); } #if 0 void vt·keypress(Vt *t, int keycode) { vt·noscroll(t); if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) { switch (keycode) { case KEY_UP: case KEY_DOWN: case KEY_RIGHT: case KEY_LEFT: { char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] }; vt·write(t, keyseq, sizeof keyseq); break; } default: vt·write(t, keytable[keycode], strlen(keytable[keycode])); } } else if (keycode <= UCHAR_MAX) { char c = keycode; vt·write(t, &c, 1); } else { fprintf(stderr, "unhandled key %#o\n", keycode); } } void vt·mouse(Vt *t, int x, int y, mmask_t mask) { char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0; if (!t->mousetrack) return; if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED)) button = 0; else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED)) button = 1; else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED)) button = 2; else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED)) button = 3; if (mask & BUTTON_SHIFT) state |= 4; if (mask & BUTTON_ALT) state |= 8; if (mask & BUTTON_CTRL) state |= 16; seq[3] = 32 + button + state; seq[4] = 32 + x; seq[5] = 32 + y; vt·write(t, seq, sizeof seq); if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) { /* send a button release event */ button = 3; seq[3] = 32 + button + state; vt·write(t, seq, sizeof seq); } } #endif static uint color_hash(short fg, short bg) { if (fg == -1) fg = COLORS; if (bg == -1) bg = COLORS + 1; return fg * (COLORS + 2) + bg; } void vt·init(void) { init_colors(); is_utf8_locale(); char *term = getenv("DVTM_TERM"); if (!term) term = "dvtm"; snprintf(vtname, sizeof vtname, "%s%s", term, COLORS >= 256 ? "-256color" : ""); } void vt·shutdown(void) { } #if 0 void vt·setkeytable(const char * const keytable_overlay[], int count) { for (int k = 0; k < count && k < KEY_MAX; k++) { const char *keyseq = keytable_overlay[k]; if (keyseq) keytable[k] = keyseq; } } void vt·settitlecb(Vt *t, vt_title_handler_t handler) { t->title_handler = handler; } void vt·seturgentcb(Vt *t, vt_urgent_handler_t handler) { t->urgent_handler = handler; } #endif void vt·setdata(Vt *t, void *data) { t->data = data; } void * vt·getdata(Vt *t) { return t->data; } bool vt·cursorvisible(Vt *t) { return t->buffer->scroll.below ? false : !t->curshid; } pid_t vt·pid(Vt *t) { return t->pid; } size_t vt·content(Vt *t, char **buf, bool colored) { Buffer *b = t->buffer; int lines = b->scroll.above + b->scroll.below + b->rows + 1; size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX)); mbstate_t ps; memset(&ps, 0, sizeof(ps)); if (!(*buf = malloc(size))) return 0; char *s = *buf; Cell *prev = nil; for (Row *row = browfirst(b); row; row = brownext(b, row)) { size_t len = 0; char *last_non_space = s; for (int col = 0; col < b->cols; col++) { Cell *cell = row->cells + col; if (colored) { int esclen = 0; if (!prev || cell->attr != prev->attr) { attr_t attr = cell->attr << NCURSES_ATTR_SHIFT; esclen = sprintf(s, "\033[0%s%s%s%s%s%sm", attr & PenBold ? ";1" : "", attr & PenDim ? ";2" : "", attr & PenUnderline ? ";4" : "", attr & PenBlink ? ";5" : "", attr & PenReverse ? ";7" : "", attr & PenInvis ? ";8" : ""); if (esclen > 0) s += esclen; } if (!prev || cell->fg != prev->fg || cell->attr != prev->attr) { if (cell->fg == -1) esclen = sprintf(s, "\033[39m"); else esclen = sprintf(s, "\033[38;5;%dm", cell->fg); if (esclen > 0) s += esclen; } if (!prev || cell->bg != prev->bg || cell->attr != prev->attr) { if (cell->bg == -1) esclen = sprintf(s, "\033[49m"); else esclen = sprintf(s, "\033[48;5;%dm", cell->bg); if (esclen > 0) s += esclen; } prev = cell; } if (cell->text) { len = wcrtomb(s, cell->text, &ps); if (len > 0) s += len; last_non_space = s; } else if (len) { len = 0; } else { *s++ = ' '; } } s = last_non_space; *s++ = '\n'; } return s - *buf; } int vt·contentstart(Vt *t) { return t->buffer->scroll.above; }