dvtm-0.12/0000755000175000017500000000000012355724771011051 5ustar marcmarcdvtm-0.12/vt.c0000644000175000017500000015652212355724771011661 0ustar marcmarc/* * Copyright © 2004 Bruno T. C. de Oliveira * Copyright © 2006 Pierre Habouzit * Copyright © 2008-2013 Marc André Tanner * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) || defined(__CYGWIN__) # include #elif defined(__FreeBSD__) # include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include #endif #if defined(__CYGWIN__) || defined(_AIX) # include #endif #include "vt.h" #ifdef _AIX # include "forkpty-aix.c" #endif #ifndef NCURSES_ATTR_SHIFT # define NCURSES_ATTR_SHIFT 8 #endif #ifndef NCURSES_ACS # ifdef PDCURSES # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)]) # else /* BSD curses */ # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)]) # endif #endif #ifdef NCURSES_VERSION # ifndef NCURSES_EXT_COLORS # define NCURSES_EXT_COLORS 0 # endif # if !NCURSES_EXT_COLORS # define MAX_COLOR_PAIRS 256 # endif #endif #ifndef MAX_COLOR_PAIRS # define MAX_COLOR_PAIRS COLOR_PAIRS #endif #ifndef CTRL # define CTRL(k) ((k) & 0x1F) #endif #define IS_CONTROL(ch) !((ch) & 0xffffff60UL) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define sstrlen(str) (sizeof(str) - 1) #define COPYMODE_ATTR A_REVERSE static bool is_utf8, has_default_colors; static short color_pairs_reserved, color_pairs_max, color_pair_current; static short *color2palette, default_fg, default_bg; static char vt_term[32] = "dvtm"; typedef struct { wchar_t *buf; wchar_t *cursor; wchar_t *end; wchar_t *display; int size; int width; int cursor_pos; mbstate_t ps; enum { CMDLINE_INACTIVE = 0, CMDLINE_INIT, CMDLINE_ACTIVE, } state; void *data; void (*callback)(void *data); char prefix; } Cmdline; typedef struct { wchar_t text; uint16_t attr; short fg; short bg; } Cell; typedef struct { Cell *cells; unsigned dirty:1; } Row; typedef struct { Row *lines; Row *curs_row; Row *scroll_buf; Row *scroll_top; Row *scroll_bot; bool *tabs; int scroll_buf_size; int scroll_buf_ptr; int scroll_amount_above; int scroll_amount_below; int rows, cols, maxcols; unsigned curattrs, savattrs; int curs_col, curs_srow, curs_scol; short curfg, curbg, savfg, savbg; } Buffer; struct Vt { Buffer buffer_normal; Buffer buffer_alternate; Buffer *buffer; unsigned defattrs; short deffg, defbg; int pty; pid_t childpid; /* flags */ unsigned seen_input:1; unsigned insert:1; unsigned escaped:1; unsigned curshid:1; unsigned curskeymode:1; unsigned copymode:1; unsigned copymode_selecting:1; unsigned bell:1; unsigned relposmode:1; unsigned mousetrack:1; unsigned graphmode:1; unsigned savgraphmode:1; bool charsets[2]; /* copymode */ int copymode_curs_srow, copymode_curs_scol; Row *copymode_sel_start_row; int copymode_sel_start_col; int copymode_cmd_multiplier; Cmdline *cmdline; /* buffers and parsing state */ char rbuf[BUFSIZ]; char ebuf[BUFSIZ]; unsigned int rlen, elen; /* last known start row, start column */ int srow, scol; /* xterm style window title */ char title[256]; vt_event_handler_t event_handler; /* custom escape sequence handler */ vt_escseq_handler_t escseq_handler; void *data; }; static const char *keytable[KEY_MAX+1] = { ['\n'] = "\r", /* for the arrow keys the CSI / SS3 sequences are not stored here * because they depend on the current cursor terminal mode */ [KEY_UP] = "A", [KEY_DOWN] = "B", [KEY_RIGHT] = "C", [KEY_LEFT] = "D", #ifdef KEY_SUP [KEY_SUP] = "\e[1;2A", #endif #ifdef KEY_SDOWN [KEY_SDOWN] = "\e[1;2B", #endif [KEY_SRIGHT] = "\e[1;2C", [KEY_SLEFT] = "\e[1;2D", [KEY_BACKSPACE] = "\177", [KEY_IC] = "\e[2~", [KEY_DC] = "\e[3~", [KEY_PPAGE] = "\e[5~", [KEY_NPAGE] = "\e[6~", [KEY_HOME] = "\e[7~", [KEY_END] = "\e[8~", [KEY_BTAB] = "\e[Z", [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */ [KEY_F(1)] = "\e[11~", [KEY_F(2)] = "\e[12~", [KEY_F(3)] = "\e[13~", [KEY_F(4)] = "\e[14~", [KEY_F(5)] = "\e[15~", [KEY_F(6)] = "\e[17~", [KEY_F(7)] = "\e[18~", [KEY_F(8)] = "\e[19~", [KEY_F(9)] = "\e[20~", [KEY_F(10)] = "\e[21~", [KEY_F(11)] = "\e[23~", [KEY_F(12)] = "\e[24~", [KEY_F(13)] = "\e[23~", [KEY_F(14)] = "\e[24~", [KEY_F(15)] = "\e[25~", [KEY_F(16)] = "\e[26~", [KEY_F(17)] = "\e[28~", [KEY_F(18)] = "\e[29~", [KEY_F(19)] = "\e[31~", [KEY_F(20)] = "\e[32~", [KEY_F(21)] = "\e[33~", [KEY_F(22)] = "\e[34~", [KEY_RESIZE] = "", #ifdef KEY_EVENT [KEY_EVENT] = "", #endif }; static void puttab(Vt *t, int count); static void process_nonprinting(Vt *t, wchar_t wc); static void send_curs(Vt *t); static void cmdline_hide_callback(void *t); static void cmdline_free(Cmdline *c); static int xwcwidth(wchar_t c) { int w = wcwidth(c); if (w == -1) w = 1; return w; } __attribute__ ((const)) static uint16_t build_attrs(unsigned curattrs) { return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff)) >> NCURSES_ATTR_SHIFT; } static void row_set(Row *row, int start, int len, Buffer *t) { Cell cell = { .text = L'\0', .attr = t ? build_attrs(t->curattrs) : 0, .fg = t ? t->curfg : -1, .bg = t ? t->curbg : -1, }; for (int i = start; i < len + start; i++) row->cells[i] = cell; row->dirty = true; } static void row_roll(Row *start, Row *end, int count) { int n = end - start; count %= n; if (count < 0) count += n; if (count) { Row *buf = alloca(count * sizeof(Row)); memcpy(buf, start, count * sizeof(Row)); memmove(start, start + count, (n - count) * sizeof(Row)); memcpy(end - count, buf, count * sizeof(Row)); for (Row *row = start; row < end; row++) row->dirty = true; } } static void clamp_cursor_to_bounds(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->curs_row < lines) b->curs_row = lines; if (b->curs_row >= lines + rows) b->curs_row = lines + rows - 1; if (b->curs_col < 0) b->curs_col = 0; if (b->curs_col >= b->cols) b->curs_col = b->cols - 1; } static void save_curs(Vt *t) { Buffer *b = t->buffer; b->curs_srow = b->curs_row - b->lines; b->curs_scol = b->curs_col; } static void restore_curs(Vt *t) { Buffer *b = t->buffer; b->curs_row = b->lines + b->curs_srow; b->curs_col = b->curs_scol; clamp_cursor_to_bounds(t); } static void save_attrs(Vt *t) { Buffer *b = t->buffer; b->savattrs = b->curattrs; b->savfg = b->curfg; b->savbg = b->curbg; t->savgraphmode = t->graphmode; } static void restore_attrs(Vt *t) { Buffer *b = t->buffer; b->curattrs = b->savattrs; b->curfg = b->savfg; b->curbg = b->savbg; t->graphmode = t->savgraphmode; } static void fill_scroll_buf(Buffer *t, int s) { /* work in screenfuls */ int ssz = t->scroll_bot - t->scroll_top; if (s > ssz) { fill_scroll_buf(t, ssz); fill_scroll_buf(t, s - ssz); return; } if (s < -ssz) { fill_scroll_buf(t, -ssz); fill_scroll_buf(t, s + ssz); return; } t->scroll_amount_above += s; if (t->scroll_amount_above >= t->scroll_buf_size) t->scroll_amount_above = t->scroll_buf_size; if (s > 0 && t->scroll_buf_size) { for (int i = 0; i < s; i++) { Row tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; t->scroll_buf[t->scroll_buf_ptr] = tmp; t->scroll_buf_ptr++; if (t->scroll_buf_ptr == t->scroll_buf_size) t->scroll_buf_ptr = 0; } } row_roll(t->scroll_top, t->scroll_bot, s); if (s < 0 && t->scroll_buf_size) { for (int i = (-s) - 1; i >= 0; i--) { t->scroll_buf_ptr--; if (t->scroll_buf_ptr == -1) t->scroll_buf_ptr = t->scroll_buf_size - 1; Row tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; t->scroll_buf[t->scroll_buf_ptr] = tmp; t->scroll_top[i].dirty = true; } } } static void cursor_line_down(Vt *t) { Buffer *b = t->buffer; row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL); b->curs_row++; if (b->curs_row < b->scroll_bot) return; vt_noscroll(t); b->curs_row = b->scroll_bot - 1; fill_scroll_buf(b, 1); row_set(b->curs_row, 0, b->cols, b); } 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->curattrs = A_NORMAL; b->curfg = b->curbg = -1; return; } for (int i = 0; i < pcount; i++) { switch (param[i]) { case 0: b->curattrs = A_NORMAL; b->curfg = b->curbg = -1; break; case 1: b->curattrs |= A_BOLD; break; case 4: b->curattrs |= A_UNDERLINE; break; case 5: b->curattrs |= A_BLINK; break; case 7: b->curattrs |= A_REVERSE; break; case 8: b->curattrs |= A_INVIS; break; case 22: b->curattrs &= ~A_BOLD; break; case 24: b->curattrs &= ~A_UNDERLINE; break; case 25: b->curattrs &= ~A_BLINK; break; case 27: b->curattrs &= ~A_REVERSE; break; case 28: b->curattrs &= ~A_INVIS; break; case 30 ... 37: /* fg */ b->curfg = param[i] - 30; break; case 38: if ((i + 2) < pcount && param[i + 1] == 5) { b->curfg = param[i + 2]; i += 2; } break; case 39: b->curfg = -1; break; case 40 ... 47: /* bg */ b->curbg = param[i] - 40; break; case 48: if ((i + 2) < pcount && param[i + 1] == 5) { b->curbg = param[i + 2]; i += 2; } break; case 49: b->curbg = -1; break; case 90 ... 97: /* hi fg */ b->curfg = param[i] - 82; break; case 100 ... 107: /* hi bg */ b->curbg = 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; save_attrs(t); b->curattrs = A_NORMAL; b->curfg = b->curbg = -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->curs_row; row_set(b->curs_row, 0, b->curs_col + 1, b); } else { row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); start = b->curs_row + 1; end = b->lines + b->rows; } for (row = start; row < end; row++) row_set(row, 0, b->cols, b); restore_attrs(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->curs_row = lines; b->curs_col = 0; } else if (pcount == 1) { b->curs_row = lines + param[0] - 1; b->curs_col = 0; } else { b->curs_row = lines + param[0] - 1; b->curs_col = param[1] - 1; } clamp_cursor_to_bounds(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->curs_row -= n; break; case 'B': case 'e': b->curs_row += n; break; case 'C': case 'a': b->curs_col += n; break; case 'D': b->curs_col -= n; break; case 'E': b->curs_row += n; b->curs_col = 0; break; case 'F': b->curs_row -= n; b->curs_col = 0; break; case 'G': case '`': b->curs_col = param[0] - 1; break; case 'd': b->curs_row = b->lines + param[0] - 1; break; } clamp_cursor_to_bounds(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: row_set(b->curs_row, 0, b->curs_col + 1, b); break; case 2: row_set(b->curs_row, 0, b->cols, b); break; default: row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); 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->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->curs_col + n > b->cols) n = b->cols - b->curs_col; for (int i = b->cols - 1; i >= b->curs_col + n; i--) row->cells[i] = row->cells[i - n]; row_set(row, b->curs_col, n, b); } /* Interpret the 'delete chars' sequence (DCH) */ static void interpret_csi_dch(Vt *t, int param[], int pcount) { Buffer *b = t->buffer; Row *row = b->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; if (b->curs_col + n > b->cols) n = b->cols - b->curs_col; for (int i = b->curs_col; i < b->cols - n; i++) row->cells[i] = row->cells[i + n]; row_set(row, b->cols - n, n, b); } /* 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->curs_row + n >= b->scroll_bot) { for (Row *row = b->curs_row; row < b->scroll_bot; row++) row_set(row, 0, b->cols, b); } else { row_roll(b->curs_row, b->scroll_bot, -n); for (Row *row = b->curs_row; row < b->curs_row + n; row++) row_set(row, 0, b->cols, b); } } /* 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->curs_row + n >= b->scroll_bot) { for (Row *row = b->curs_row; row < b->scroll_bot; row++) row_set(row, 0, b->cols, b); } else { row_roll(b->curs_row, b->scroll_bot, n); for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++) row_set(row, 0, b->cols, b); } } /* 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->curs_col + n > b->cols) n = b->cols - b->curs_col; row_set(b->curs_row, b->curs_col, n, b); } /* 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->curs_row = b->scroll_top; b->curs_col = 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 47: /* use alternate/normal screen buffer */ vt_copymode_leave(t); t->buffer = set ? &t->buffer_alternate : &t->buffer_normal; vt_dirty(t); break; case 1000: /* enable/disable normal mouse tracking */ t->mousetrack = set; break; } } } static void interpret_csi(Vt *t) { static int csiparam[BUFSIZ]; Buffer *b = t->buffer; int 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 >= (int)sizeof(csiparam)) return; /* too long! */ csiparam[param_count++] = 0; } else if (isdigit((unsigned char)*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 (csiparam[0]) { case 0: b->tabs[b->curs_col] = false; break; case 3: memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols); break; } case 'r': /* set scrolling region */ interpret_csi_decstbm(t, csiparam, param_count); break; case 's': /* save cursor location */ save_curs(t); break; case 'u': /* restore cursor location */ restore_curs(t); break; case 'n': /* query cursor location */ if (param_count == 1 && csiparam[0] == 6) send_curs(t); break; default: break; } } /* Interpret an 'index' (IND) sequence */ static void interpret_csi_ind(Vt *t) { Buffer *b = t->buffer; if (b->curs_row < b->lines + b->rows - 1) b->curs_row++; } /* Interpret a 'reverse index' (RI) sequence */ static void interpret_csi_ri(Vt *t) { Buffer *b = t->buffer; if (b->curs_row > b->scroll_top) b->curs_row--; else { row_roll(b->scroll_top, b->scroll_bot, -1); row_set(b->scroll_top, 0, b->cols, b); } } /* Interpret a 'next line' (NEL) sequence */ static void interpret_csi_nel(Vt *t) { Buffer *b = t->buffer; if (b->curs_row < b->lines + b->rows - 1) { b->curs_row++; b->curs_col = 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 xterm specific escape sequences */ static void interpret_esc_xterm(Vt *t) { /* ESC]n;dataBEL -- the ESC is not part of t->ebuf */ char *title = NULL; switch (t->ebuf[1]) { case '0': case '2': t->ebuf[t->elen - 1] = '\0'; if (t->elen > sstrlen("]n;\a")) title = t->ebuf + sstrlen("]n;"); if (t->event_handler) t->event_handler(t, VT_EVENT_TITLE, title); } } static void try_interpret_escape_seq(Vt *t) { char lastchar = t->ebuf[t->elen - 1]; if (!*t->ebuf) return; if (t->escseq_handler) { switch ((*(t->escseq_handler)) (t, t->ebuf)) { case VT_ESCSEQ_HANDLER_OK: cancel_escape_sequence(t); return; case VT_ESCSEQ_HANDLER_NOTYET: if (t->elen + 1 >= sizeof(t->ebuf)) goto cancel; 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 ']': /* xterm thing */ if (lastchar == '\a' || (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) { interpret_esc_xterm(t); goto handled; } break; case '[': if (is_valid_csi_ender(lastchar)) { interpret_csi(t); goto handled; } break; case '7': /* DECSC: save cursor and attributes */ save_attrs(t); save_curs(t); goto handled; case '8': /* DECRC: restore cursor and attributes */ restore_attrs(t); restore_curs(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->curs_col] = true; goto handled; default: goto cancel; } if (t->elen + 1 >= sizeof(t->ebuf)) { cancel: #ifndef NDEBUG fprintf(stderr, "cancelled: \\033"); for (unsigned int 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; int col = b->curs_col + direction; while (count) { if (col < 0) { b->curs_col = 0; break; } if (col >= b->cols) { b->curs_col = b->cols - 1; break; } if (b->tabs[col]) { b->curs_col = col; count -= direction; } col += direction; } } static void process_nonprinting(Vt *t, wchar_t wc) { Buffer *b = t->buffer; switch (wc) { case '\e': /* ESC */ new_escape_sequence(t); break; case '\a': /* BEL */ if (t->bell) beep(); break; case '\b': /* BS */ if (b->curs_col > 0) b->curs_col--; break; case '\t': /* HT */ puttab(t, 1); break; case '\r': /* CR */ b->curs_col = 0; break; case '\v': /* VT */ case '\f': /* FF */ case '\n': /* LF */ cursor_line_down(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"; is_utf8 = !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 (is_utf8) return vt100_utf8[c - 0x41]; else if (strchr(vt100_acs, c)) return NCURSES_ACS(c); return '\0'; } static void put_wc(Vt *t, wchar_t wc) { int width = 0; if (!t->seen_input) { t->seen_input = 1; kill(-t->childpid, SIGWINCH); } if (t->escaped) { if (t->elen + 1 < sizeof(t->ebuf)) { t->ebuf[t->elen] = wc; t->ebuf[++t->elen] = '\0'; try_interpret_escape_seq(t); } else { cancel_escape_sequence(t); } } else if (IS_CONTROL(wc)) { process_nonprinting(t, wc); } else { if (t->graphmode) { if (wc >= 0x41 && wc <= 0x7e) { wchar_t gc = get_vt100_graphic(wc); if (gc) wc = gc; } width = 1; } else if ((width = wcwidth(wc)) < 1) { width = 1; } Buffer *b = t->buffer; Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg }; if (width == 2 && b->curs_col == b->cols - 1) { b->curs_row->cells[b->curs_col++] = blank_cell; b->curs_row->dirty = true; } if (b->curs_col >= b->cols) { b->curs_col = 0; cursor_line_down(t); } if (t->insert) { Cell *src = b->curs_row->cells + b->curs_col; Cell *dest = src + width; size_t len = b->cols - b->curs_col - width; memmove(dest, src, len * sizeof *dest); } b->curs_row->cells[b->curs_col] = blank_cell; b->curs_row->cells[b->curs_col++].text = wc; b->curs_row->dirty = true; if (width == 2) b->curs_row->cells[b->curs_col++] = blank_cell; } } int vt_process(Vt *t) { int res; unsigned int 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) { wchar_t wc; ssize_t len; len = (ssize_t)mbrtowc(&wc, 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; wc = t->rbuf[pos]; } pos += len ? len : 1; put_wc(t, wc); } t->rlen -= pos; memmove(t->rbuf, t->rbuf + pos, t->rlen); return 0; } void vt_set_default_colors(Vt *t, unsigned attrs, short fg, short bg) { t->defattrs = attrs; t->deffg = fg; t->defbg = bg; } static void buffer_free(Buffer *t) { for (int i = 0; i < t->rows; i++) free(t->lines[i].cells); free(t->lines); for (int i = 0; i < t->scroll_buf_size; i++) free(t->scroll_buf[i].cells); free(t->scroll_buf); free(t->tabs); } static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_size) { Row *lines, *scroll_buf; t->lines = lines = calloc(rows, sizeof(Row)); if (!lines) return false; t->curattrs = A_NORMAL; /* white text over black background */ t->curfg = t->curbg = -1; for (Row *row = lines, *end = lines + rows; row < end; row++) { row->cells = malloc(cols * sizeof(Cell)); if (!row->cells) { t->rows = row - lines; goto fail; } row_set(row, 0, cols, NULL); } t->rows = rows; if (scroll_buf_size < 0) scroll_buf_size = 0; t->scroll_buf = scroll_buf = calloc(scroll_buf_size, sizeof(Row)); if (!scroll_buf && scroll_buf_size) goto fail; for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_size; row < end; row++) { row->cells = calloc(cols, sizeof(Cell)); if (!row->cells) { t->scroll_buf_size = row - scroll_buf; goto fail; } } t->tabs = calloc(cols, sizeof(*t->tabs)); if (!t->tabs) goto fail; for (int col = 8; col < cols; col += 8) t->tabs[col] = true; t->curs_row = lines; t->curs_col = 0; /* initial scrolling area is the whole window */ t->scroll_top = lines; t->scroll_bot = lines + rows; t->scroll_buf_size = scroll_buf_size; t->maxcols = t->cols = cols; return true; fail: buffer_free(t); return false; } Vt *vt_create(int rows, int cols, int scroll_buf_size) { Vt *t; if (rows <= 0 || cols <= 0) return NULL; t = calloc(1, sizeof(Vt)); if (!t) return NULL; t->pty = -1; t->deffg = t->defbg = -1; if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_size) || !buffer_init(&t->buffer_alternate, rows, cols, 0)) { free(t); return NULL; } t->buffer = &t->buffer_normal; t->copymode_cmd_multiplier = 0; return t; } static void buffer_resize(Buffer *t, int rows, int cols) { Row *lines = t->lines; if (t->rows != rows) { if (t->curs_row > lines + rows) { /* scroll up instead of simply chopping off bottom */ fill_scroll_buf(t, (t->curs_row - t->lines) - rows + 1); } while (t->rows > rows) { free(lines[t->rows - 1].cells); t->rows--; } lines = realloc(lines, sizeof(Row) * rows); } if (t->maxcols < cols) { for (int row = 0; row < t->rows; row++) { lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols); if (t->cols < cols) row_set(lines + row, t->cols, cols - t->cols, NULL); lines[row].dirty = true; } Row *sbuf = t->scroll_buf; for (int row = 0; row < t->scroll_buf_size; row++) { sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols); if (t->cols < cols) row_set(sbuf + row, t->cols, cols - t->cols, NULL); } t->tabs = realloc(t->tabs, sizeof(*t->tabs) * cols); for (int col = t->cols; col < cols; col++) t->tabs[col] = !(col & 7); t->maxcols = cols; t->cols = cols; } else if (t->cols != cols) { for (int row = 0; row < t->rows; row++) lines[row].dirty = true; t->cols = cols; } int deltarows = 0; if (t->rows < rows) { while (t->rows < rows) { lines[t->rows].cells = calloc(t->maxcols, sizeof(Cell)); row_set(lines + t->rows, 0, t->maxcols, t); t->rows++; } /* prepare for backfill */ if (t->curs_row >= t->scroll_bot - 1) { deltarows = t->lines + rows - t->curs_row - 1; if (deltarows > t->scroll_amount_above) deltarows = t->scroll_amount_above; } } t->curs_row += lines - t->lines; t->scroll_top = lines; t->scroll_bot = lines + rows; t->lines = lines; /* perform backfill */ if (deltarows > 0) { fill_scroll_buf(t, -deltarows); t->curs_row += deltarows; } } 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); if (t->copymode) vt_copymode_leave(t); buffer_resize(&t->buffer_normal, rows, cols); buffer_resize(&t->buffer_alternate, rows, cols); clamp_cursor_to_bounds(t); ioctl(t->pty, TIOCSWINSZ, &ws); kill(-t->childpid, SIGWINCH); } void vt_destroy(Vt *t) { if (!t) return; buffer_free(&t->buffer_normal); buffer_free(&t->buffer_alternate); cmdline_free(t->cmdline); 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; } static void copymode_get_selection_boundry(Vt *t, Row **start_row, int *start_col, Row **end_row, int *end_col, bool clip) { Buffer *b = t->buffer; if (t->copymode_sel_start_row >= b->lines && t->copymode_sel_start_row < b->lines + b->rows) { /* within the current page */ if (b->curs_row >= t->copymode_sel_start_row) { *start_row = t->copymode_sel_start_row; *end_row = b->curs_row; *start_col = t->copymode_sel_start_col; *end_col = b->curs_col; } else { *start_row = b->curs_row; *end_row = t->copymode_sel_start_row; *start_col = b->curs_col; *end_col = t->copymode_sel_start_col; } if (b->curs_col < *start_col && *start_row == *end_row) { *start_col = b->curs_col; *end_col = t->copymode_sel_start_col; } } else { /* part of the scrollback buffer is also selected */ if (t->copymode_sel_start_row < b->lines) { /* above the current page */ if (clip) { *start_row = b->lines; *start_col = 0; } else { int copied_lines = b->lines - t->copymode_sel_start_row; *start_row = &b->scroll_buf [(b->scroll_buf_ptr - copied_lines + b->scroll_buf_size) % b->scroll_buf_size]; *start_col = t->copymode_sel_start_col; } *end_row = b->curs_row; *end_col = b->curs_col; } else { /* below the current page */ *start_row = b->curs_row; *start_col = b->curs_col; if (clip) { *end_row = b->lines + b->rows; *end_col = b->cols - 1; } else { int copied_lines = t->copymode_sel_start_row -(b->lines + b->rows); *end_row = &b->scroll_buf [(b->scroll_buf_ptr + copied_lines) % b->scroll_buf_size]; *end_col = t->copymode_sel_start_col; } } } } void vt_draw(Vt *t, WINDOW * win, int srow, int scol) { Buffer *b = t->buffer; bool sel = false; Row *sel_row_start, *sel_row_end; int sel_col_start, sel_col_end; if (srow != t->srow || scol != t->scol) { vt_dirty(t); t->srow = srow; t->scol = scol; } copymode_get_selection_boundry(t, &sel_row_start, &sel_col_start, &sel_row_end, &sel_col_end, true); for (int i = 0; i < b->rows; i++) { Row *row = b->lines + i; if (!row->dirty) continue; wmove(win, srow + i, scol); Cell *cell = NULL; for (int j = 0; j < b->cols; j++) { Cell *prev_cell = cell; cell = row->cells + j; if (!prev_cell || cell->attr != prev_cell->attr || cell->fg != prev_cell->fg || cell->bg != prev_cell->bg) { if (cell->attr == A_NORMAL) cell->attr = t->defattrs; if (cell->fg == -1) cell->fg = t->deffg; if (cell->bg == -1) cell->bg = t->defbg; wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT); wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); } if (t->copymode_selecting && ((row > sel_row_start && row < sel_row_end) || (row == sel_row_start && j >= sel_col_start && (row != sel_row_end || j <= sel_col_end)) || (row == sel_row_end && j <= sel_col_end && (row != sel_row_start || j >= sel_col_start)))) { wattrset(win, (attr_t) ((cell->attr << NCURSES_ATTR_SHIFT)|COPYMODE_ATTR)); sel = true; } else if (sel) { wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT); wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); sel = false; } if (is_utf8 && cell->text >= 128) { char buf[MB_CUR_MAX + 1]; size_t len = wcrtomb(buf, cell->text, NULL); if (len > 0) { waddnstr(win, buf, len); if (wcwidth(cell->text) > 1) j++; } } else { waddch(win, cell->text > ' ' ? cell->text : ' '); } } 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->curs_row - b->lines, scol + b->curs_col); if (t->cmdline && t->cmdline->state) { wattrset(win, t->defattrs << NCURSES_ATTR_SHIFT); mvwaddch(win, srow + b->rows - 1, 0, t->cmdline->prefix); whline(win, ' ', b->cols - 1); if (t->cmdline->state == CMDLINE_ACTIVE) { waddnwstr(win, t->cmdline->display, b->cols - 1); wmove(win, srow + b->rows - 1, 1 + t->cmdline->cursor_pos); } else wmove(win, srow + b->rows - 1, 1); } } void vt_scroll(Vt *t, int rows) { Buffer *b = t->buffer; if (!b->scroll_buf_size) return; if (rows < 0) { /* scroll back */ if (rows < -b->scroll_amount_above) rows = -b->scroll_amount_above; } else { /* scroll forward */ if (rows > b->scroll_amount_below) rows = b->scroll_amount_below; } fill_scroll_buf(b, rows); b->scroll_amount_below -= rows; if (t->copymode_selecting) t->copymode_sel_start_row -= rows; } void vt_noscroll(Vt *t) { int scroll_amount_below = t->buffer->scroll_amount_below; if (scroll_amount_below) vt_scroll(t, scroll_amount_below); } void vt_bell(Vt *t, bool bell) { t->bell = bell; } void vt_togglebell(Vt *t) { t->bell = !t->bell; } pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *pty) { struct winsize ws; pid_t pid; const char **envp = env; int fd, maxfd; ws.ws_row = t->buffer->rows; ws.ws_col = t->buffer->cols; ws.ws_xpixel = ws.ws_ypixel = 0; pid = forkpty(&t->pty, NULL, NULL, &ws); if (pid < 0) return -1; if (pid == 0) { setsid(); maxfd = sysconf(_SC_OPEN_MAX); for (fd = 3; fd < maxfd; fd++) if (close(fd) == -1 && errno == EBADF) break; while (envp && envp[0]) { setenv(envp[0], envp[1], 1); envp += 2; } setenv("TERM", vt_term, 1); if (cwd) chdir(cwd); execv(p, (char *const *)argv); fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]); exit(1); } if (pty) *pty = t->pty; return t->childpid = pid; } int vt_getpty(Vt *t) { return t->pty; } int vt_write(Vt *t, const char *buf, int len) { int ret = len; while (len > 0) { int res = write(t->pty, buf, len); if (res < 0) { if (errno != EAGAIN && errno != EINTR) return -1; else continue; } buf += res; len -= res; } return ret; } static void send_curs(Vt *t) { Buffer *b = t->buffer; char keyseq[16]; snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col); vt_write(t, keyseq, strlen(keyseq)); } 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 { #ifndef NDEBUG fprintf(stderr, "unhandled key %#o\n", keycode); #endif } } static Row *buffer_next_row(Buffer *t, Row *row, int direction) { bool has_scroll_buf = t->scroll_buf_size > 0; Row *before_start_row, *before_end_row, *after_start_row, *after_end_row; Row *first_row = t->lines; Row *last_row = t->lines + t->rows - 1; if (has_scroll_buf) { before_end_row = &t->scroll_buf [(t->scroll_buf_ptr - 1 + t->scroll_buf_size) % t->scroll_buf_size]; before_start_row = &t->scroll_buf [(t->scroll_buf_ptr - t->scroll_amount_above + t->scroll_buf_size) % t->scroll_buf_size]; after_start_row = &t->scroll_buf[t->scroll_buf_ptr]; after_end_row = &t->scroll_buf [(t->scroll_buf_ptr + t->scroll_amount_below - 1) % t->scroll_buf_size]; } if (direction > 0) { if (row >= first_row && row < last_row) return ++row; if (row == last_row) { if (has_scroll_buf) { if (t->scroll_amount_below) return after_start_row; else if (t->scroll_amount_above) return before_start_row; } return first_row; } if (row == before_end_row) return first_row; if (row == after_end_row) return t->scroll_amount_above ? before_start_row : first_row; if (row == &t->scroll_buf[t->scroll_buf_size - 1]) return t->scroll_buf; return ++row; } else { if (row > first_row && row <= last_row) return --row; if (row == first_row) { if (has_scroll_buf) { if (t->scroll_amount_above) return before_end_row; else if (t->scroll_amount_below) return after_end_row; } return last_row; } if (row == before_start_row) return t->scroll_amount_below ? after_end_row : last_row; if (row == after_start_row) return last_row; if (row == t->scroll_buf) return &t->scroll_buf[t->scroll_buf_size - 1]; return --row; } } static void row_show(Vt *t, Row *r) { Buffer *b = t->buffer; int below = b->scroll_amount_below; int above = b->scroll_amount_above; int ptr = b->scroll_buf_ptr; int size = b->scroll_buf_size; int row = r - b->scroll_buf; int scroll = 0; if (b->lines <= r && r < b->lines + b->rows) { b->curs_row = r; return; } if (!size) return; if (row < ptr) { if (row - ptr + size < below) scroll = row - ptr + size + 1; else if (ptr - row <= above) scroll = row - ptr; } else { if (row - ptr < below) scroll = row - ptr + 1; else if (ptr - row + size <= above) scroll = row - ptr - size; } if (scroll) { vt_scroll(t, scroll); b->curs_row = b->lines + (scroll > 0 ? b->rows - 1 : 0); } } static void copymode_search(Vt *t, int direction) { wchar_t *searchbuf = t->cmdline ? t->cmdline->buf : NULL; if (!searchbuf || *searchbuf == '\0') return; Buffer *b = t->buffer; /* avoid match at current cursor position */ Row *start_row = b->curs_row; int start_col = b->curs_col + direction; if (start_col >= b->cols) { start_col = 0; start_row = buffer_next_row(b, start_row, 1); } else if (start_col < 0) { start_col = b->cols - 1; start_row = buffer_next_row(b, start_row, -1); } Row *row = start_row, *matched_row = NULL; int matched_col = 0; int len = wcslen(searchbuf) - 1; int s_start = direction > 0 ? 0 : len; int s_end = direction > 0 ? len : 0; int s = s_start; for (;;) { int col = direction > 0 ? 0 : b->cols - 1; if (row == start_row) col = start_col; for (; col >= 0 && col < b->cols; col += direction) { if (searchbuf[s] == row->cells[col].text) { if (s == s_start) { matched_row = row; matched_col = col; } if (s == s_end) { b->curs_col = matched_col; if (matched_row) row_show(t, matched_row); return; } s += direction; int width = wcwidth(searchbuf[s]); if (width < 0) width = 0; else if (width >= 1) width--; col += direction > 0 ? width : -width; } else if (searchbuf[s_start] == row->cells[col].text) { s = s_start + direction; matched_row = row; matched_col = col; } else { s = s_start; } } if ((row = buffer_next_row(b, row, direction)) == start_row) break; } } static void cmdline_hide_callback(void *t) { Buffer *b = ((Vt *)t)->buffer; b->lines[b->rows - 1].dirty = true; } static void cmdline_show(Cmdline *c, char prefix, int width, void (*callback)(void *t), void *data) { if (!c) return; c->callback = callback; memset(&c->ps, 0, sizeof(mbstate_t)); if (!c->buf) { c->size = width+1; c->buf = calloc(c->size, sizeof(wchar_t)); c->cursor = c->end = c->display = c->buf; } if (!c->buf) return; c->state = CMDLINE_INIT; c->data = data; c->prefix = prefix; c->width = width - (prefix ? 1 : 0); } static void cmdline_hide(Cmdline *c) { if (!c) return; c->state = CMDLINE_INACTIVE; c->callback(c->data); } static void cmdline_adjust_cursor_pos(Cmdline *c) { int pos = 0, w; for (wchar_t *cur = c->display; cur < c->cursor; cur++) pos += xwcwidth(*cur); int extraspace = pos - c->width + 1; if (extraspace > 0) { for (w = 0; w < extraspace; w += xwcwidth(*c->display++)); c->cursor_pos = pos - w; } else c->cursor_pos = pos; } static void cmdline_keypress(Cmdline *c, int keycode) { if (keycode != KEY_UP && c->state == CMDLINE_INIT) { c->buf[0] = L'\0'; c->display = c->cursor = c->end = c->buf; c->cursor_pos = 0; } char keychar = (char)keycode; wchar_t wc = L'\0'; int width = -1; ssize_t len; size_t n = (c->end - c->cursor) * sizeof(wchar_t); switch (keycode) { case KEY_DC: if (c->cursor == c->end) /* n == 0 */ break; memmove(c->cursor, c->cursor + 1, n - sizeof(wchar_t)); c->end--; *c->end = L'\0'; cmdline_adjust_cursor_pos(c); break; case KEY_BACKSPACE: if (c->cursor == c->buf) break; memmove(c->cursor - 1, c->cursor, n); if (c->end > c->buf) c->end--; *c->end = L'\0'; case KEY_LEFT: if (c->cursor > c->buf) c->cursor--; if (c->cursor_pos == 0) { c->display -= c->width / 2; if (c->display < c->buf) c->display = c->buf; } cmdline_adjust_cursor_pos(c); break; case KEY_UP: break; case KEY_DOWN: c->buf[0] = L'\0'; c->end = c->buf; case KEY_HOME: c->display = c->cursor = c->buf; c->cursor_pos = 0; break; case KEY_END: c->cursor = c->end; width = 0; wchar_t *disp = c->end; while (disp >= c->buf) { int tmp = xwcwidth(*disp); if (width + tmp > c->width - 1) break; width += tmp; disp--; } c->display = disp >= c->buf ? disp + 1: c->buf; c->cursor_pos = (width < c->width - 1) ? width : c->width - 1; break; case CTRL('a') ... CTRL('z'): if (keycode != '\n') break; copymode_search(c->data, c->prefix == '/' ? 1 : -1); case '\e': cmdline_hide(c); return; default: len = (ssize_t)mbrtowc(&wc, &keychar, 1, &c->ps); if (len == -2) return; if (len == -1) wc = keycode; width = xwcwidth(wc); if (c->end - c->buf >= c->size - 2) { c->size *= 2; wchar_t *buf = realloc(c->buf, c->size * sizeof(wchar_t)); if (!buf) return; ptrdiff_t diff = buf - c->buf; c->cursor += diff; c->end += diff; c->display += diff; c->buf = buf; } if (c->cursor < c->end) memmove(c->cursor + 1, c->cursor, n); *c->cursor = wc; *(++c->end) = L'\0'; case KEY_RIGHT: if (c->cursor_pos == c->width - 1) { if (c->cursor < c->end) c->cursor++; if (c->cursor != c->end) { c->display += c->width / 2; if (c->display > c->end) c->display = c->buf; } cmdline_adjust_cursor_pos(c); } else { if (width == -1) width = xwcwidth(*c->cursor); c->cursor_pos += width; if (c->cursor_pos > c->width - 1) c->cursor_pos = c->width - 1; if (c->cursor < c->end) c->cursor++; } break; } c->state = CMDLINE_ACTIVE; } static void cmdline_free(Cmdline *c) { if (!c) return; free(c->buf); free(c); } void vt_copymode_keypress(Vt *t, int keycode) { Buffer *b = t->buffer; Row *start_row, *end_row; int direction, col, start_col, end_col, delta, scroll_page = b->rows / 2; char *copybuf, keychar = (char)keycode; bool found; if (!t->copymode) return; if (t->cmdline && t->cmdline->state) { cmdline_keypress(t->cmdline, keycode); } else { switch (keycode) { case '0': if (t->copymode_cmd_multiplier == 0) { b->curs_col = 0; break; } /* fall through */ case '1' ... '9': t->copymode_cmd_multiplier = (t->copymode_cmd_multiplier * 10) + (keychar - '0'); return; case KEY_PPAGE: case CTRL('u'): delta = b->curs_row - b->lines; if (delta > scroll_page) b->curs_row -= scroll_page; else { b->curs_row = b->lines; vt_scroll(t, delta - scroll_page); } break; case KEY_NPAGE: case CTRL('d'): delta = b->rows - (b->curs_row - b->lines); if (delta > scroll_page) b->curs_row += scroll_page; else { b->curs_row = b->lines + b->rows - 1; vt_scroll(t, scroll_page - delta); } break; case 'g': if (b->scroll_amount_above) vt_scroll(t, -b->scroll_amount_above); /* fall through */ case 'H': b->curs_row = b->lines; break; case 'M': b->curs_row = b->lines + (b->rows / 2); break; case 'G': vt_noscroll(t); /* fall through */ case 'L': b->curs_row = b->lines + b->rows - 1; break; case KEY_HOME: case '^': b->curs_col = 0; break; case KEY_END: case '$': start_col = b->cols - 1; for (int i = 0; i < b->cols; i++) if (b->curs_row->cells[i].text) start_col = i; b->curs_col = start_col; break; case '/': case '?': if (!t->cmdline) t->cmdline = calloc(1, sizeof(Cmdline)); cmdline_show(t->cmdline, keycode, b->cols, cmdline_hide_callback, t); break; case 'n': case 'N': copymode_search(t, keycode == 'n' ? 1 : -1); break; case 'v': t->copymode_selecting = true; t->copymode_sel_start_row = b->curs_row; t->copymode_sel_start_col = b->curs_col; break; case 'y': if (!t->copymode_selecting) { b->curs_col = 0; t->copymode_sel_start_row = b->curs_row + (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier - 1 : 0); if (t->copymode_sel_start_row >= b->lines + b->rows) t->copymode_sel_start_row = b->lines + b->rows - 1; t->copymode_sel_start_col = b->cols - 1; } copymode_get_selection_boundry(t, &start_row, &start_col, &end_row, &end_col, false); int line_count = t->copymode_sel_start_row > b->curs_row ? t->copymode_sel_start_row - b->curs_row : b->curs_row - t->copymode_sel_start_row; copybuf = calloc(1, (line_count + 1) * b->cols * MB_CUR_MAX + 1); if (copybuf) { char *s = copybuf; mbstate_t ps; memset(&ps, 0, sizeof(ps)); Row *row = start_row; for (;;) { char *last_non_space = s; int j = (row == start_row) ? start_col : 0; int col = (row == end_row) ? end_col : b->cols - 1; for (size_t len = 0; j <= col; j++) { if (row->cells[j].text) { len = wcrtomb(s, row->cells[j].text, &ps); if (len > 0) s += len; last_non_space = s; } else if (len) { len = 0; } else { *s++ = ' '; } } s = last_non_space; if (row == end_row) break; else *s++ = '\n'; row = buffer_next_row(b, row, 1); } *s = '\0'; if (t->event_handler) t->event_handler(t, VT_EVENT_COPY_TEXT, copybuf); } /* fall through */ case '\e': case 'q': case CTRL('c'): vt_copymode_leave(t); return; default: for (int c = 0; c < (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier : 1); c++) { int width; switch (keycode) { case 'w': case 'W': case 'b': case 'B': direction = (keycode == 'w' || keycode == 'W') ? 1 : -1; start_col = (direction > 0) ? 0 : b->cols - 1; end_col = (direction > 0) ? b->cols - 1 : 0; col = b->curs_col; found = false; do { for (;;) { if (b->curs_row->cells[col].text == ' ') { found = true; break; } if (col == end_col) break; col += direction; } if (found) { while (b->curs_row->cells[col].text == ' ') { if (col == end_col) { b->curs_row += direction; break; } col += direction; } } else { col = start_col; b->curs_row += direction; } if (b->curs_row < b->lines) { b->curs_row = b->lines; if (b->scroll_amount_above) vt_scroll(t, -1); else break; } if (b->curs_row >= b->lines + b->rows) { b->curs_row = b->lines + b->rows - 1; if (b->scroll_amount_below) vt_scroll(t, 1); else break; } } while (!found); if (found) b->curs_col = col; break; case KEY_UP: case 'k': if (b->curs_row == b->lines) vt_scroll(t, -1); else b->curs_row--; break; case KEY_DOWN: case 'j': if (b->curs_row == b->lines + b->rows - 1) vt_scroll(t, 1); else b->curs_row++; break; case KEY_RIGHT: case 'l': width = wcwidth(b->curs_row->cells[b->curs_col].text); b->curs_col += width < 1 ? 1 : width; if (b->curs_col >= b->cols) { b->curs_col = b->cols - 1; t->copymode_cmd_multiplier = 0; } break; case KEY_LEFT: case 'h': width = 1; if (b->curs_col >= 2 && !b->curs_row->cells[b->curs_col-1].text) width = wcwidth(b->curs_row->cells[b->curs_col-2].text); b->curs_col -= width < 1 ? 1 : width; if (b->curs_col < 0) { b->curs_col = 0; t->copymode_cmd_multiplier = 0; } break; } } break; } } if (t->copymode_selecting) vt_dirty(t); t->copymode_cmd_multiplier = 0; } void vt_mouse(Vt *t, int x, int y, mmask_t mask) { #ifdef NCURSES_MOUSE_VERSION 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 /* NCURSES_MOUSE_VERSION */ } static unsigned int color_hash(short fg, short bg) { if (fg == -1) fg = COLORS; if (bg == -1) bg = COLORS + 1; return fg * (COLORS + 2) + bg; } short vt_color_get(Vt *t, short fg, short bg) { if (fg >= COLORS) fg = (t ? t->deffg : default_fg); if (bg >= COLORS) bg = (t ? t->defbg : default_bg); if (!has_default_colors) { if (fg == -1) fg = (t && t->deffg != -1 ? t->deffg : default_fg); if (bg == -1) bg = (t && t->defbg != -1 ? t->defbg : default_bg); } if (!color2palette || (fg == -1 && bg == -1)) return 0; unsigned int index = color_hash(fg, bg); if (color2palette[index] == 0) { short oldfg, oldbg; for (;;) { if (++color_pair_current >= color_pairs_max) color_pair_current = color_pairs_reserved + 1; pair_content(color_pair_current, &oldfg, &oldbg); unsigned int old_index = color_hash(oldfg, oldbg); if (color2palette[old_index] >= 0) { if (init_pair(color_pair_current, fg, bg) == OK) { color2palette[old_index] = 0; color2palette[index] = color_pair_current; } break; } } } short color_pair = color2palette[index]; return color_pair >= 0 ? color_pair : -color_pair; } short vt_color_reserve(short fg, short bg) { if (!color2palette || fg >= COLORS || bg >= COLORS) return 0; if (!has_default_colors && fg == -1) fg = default_fg; if (!has_default_colors && bg == -1) bg = default_bg; if (fg == -1 && bg == -1) return 0; unsigned int index = color_hash(fg, bg); if (color2palette[index] >= 0) { if (init_pair(++color_pairs_reserved, fg, bg) == OK) color2palette[index] = -color_pairs_reserved; } short color_pair = color2palette[index]; return color_pair >= 0 ? color_pair : -color_pair; } static void init_colors(void) { pair_content(0, &default_fg, &default_bg); if (default_fg == -1) default_fg = COLOR_WHITE; if (default_bg == -1) default_bg = COLOR_BLACK; has_default_colors = (use_default_colors() == OK); color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS); if (COLORS) color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); vt_color_reserve(COLOR_WHITE, COLOR_BLACK); } void vt_init(void) { init_colors(); is_utf8_locale(); char color_suffix[] = "-256color"; char *term = getenv("DVTM_TERM"); if (term) strncpy(vt_term, term, sizeof(vt_term) - sizeof(color_suffix)); if (COLORS >= 256) strncat(vt_term, color_suffix, sizeof(color_suffix) - 1); } void vt_set_keytable(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_shutdown(void) { free(color2palette); } void vt_set_escseq_handler(Vt *t, vt_escseq_handler_t handler) { t->escseq_handler = handler; } void vt_set_event_handler(Vt *t, vt_event_handler_t handler) { t->event_handler = handler; } void vt_set_data(Vt *t, void *data) { t->data = data; } void *vt_get_data(Vt *t) { return t->data; } unsigned vt_cursor(Vt *t) { if (t->copymode) return 1; return t->buffer->scroll_amount_below ? 0 : !t->curshid; } unsigned vt_copymode(Vt *t) { return t->copymode; } void vt_copymode_enter(Vt *t) { if (t->copymode) return; Buffer *b = t->buffer; t->copymode_curs_srow = b->curs_row - b->lines; t->copymode_curs_scol = b->curs_col; t->copymode = true; } void vt_copymode_leave(Vt *t) { if (!t->copymode) return; Buffer *b = t->buffer; t->copymode = false; t->copymode_selecting = false; t->copymode_sel_start_row = b->lines; t->copymode_sel_start_col = 0; t->copymode_cmd_multiplier = 0; b->curs_row = b->lines + t->copymode_curs_srow; b->curs_col = t->copymode_curs_scol; cmdline_hide(t->cmdline); vt_noscroll(t); vt_dirty(t); } dvtm-0.12/dvtm-status0000755000175000017500000000032712355724771013274 0ustar marcmarc#!/bin/sh FIFO="/tmp/dvtm-status.$$" [ -e "$FIFO" ] || mkfifo "$FIFO" chmod 600 $FIFO while true; do date +%H:%M sleep 60 done > $FIFO & STATUS_PID=$! dvtm -s $FIFO "$@" 2> /dev/null kill $STATUS_PID rm $FIFO dvtm-0.12/dvtm.info0000644000175000017500000000417512355724771012707 0ustar marcmarcdvtm|dynamic virtual terminal manager, am, eo, mir, msgr, xenl, colors#8, cols#80, it#8, lines#24, ncv@, pairs#64, acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l, clear=\E[H\E[2J, cnorm=\E[?25h, cr=^M, csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H, cud=\E[%p1%dB, cud1=^J, cuf=\E[%p1%dC, cuf1=\E[C, cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A, dl=\E[%p1%dM, dl1=\E[M, ed=\E[J, el=\E[K, el1=\E[1K, enacs=\E(B\E)0, home=\E[H, hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@, ich1=\E[@, il=\E[%p1%dL, il1=\E[L, ind=^J, is1=\E[?47l\E=\E[?1l, is2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l, kDC=\E[3$, kEND=\E[8$, kHOM=\E[7$, kIC=\E[2$, kLFT=\E[d, kNXT=\E[6$, kPRV=\E[5$, kRIT=\E[c, ka1=\EOw, ka3=\EOy, kb2=\EOu, kbs=\177, kc1=\EOq, kc3=\EOs, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B, kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kel=\E[8\^, kend=\E[8~, kent=\EOM, kf0=\E[21~, kf1=\E[11~, kf2=\E[12~, kf3=\E[13~, kf4=\E[14~, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~, kf8=\E[19~, kf9=\E[20~, kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~, kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~, kf18=\E[32~, kf19=\E[33~, kf20=\E[34~, kf21=\E[23$, kf22=\E[24$ kfnd=\E[1~, khome=\E[7~, kich1=\E[2~, kind=\E[a, kmous=\E[M, knp=\E[6~, kpp=\E[5~, kri=\E[b, kslt=\E[4~, op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, rmacs=^O, rmcup=\E[2J\E[?47l\E8, rmir=\E[4l, rmso=\E[27m, rmul=\E[24m, rs1=\E>\E[?1;3;4;5;6l\E[?7h\E[m\E[r\E[2J\E[H, rs2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l\E>\E[?1000l\E[?25h, s0ds=\E(B, s1ds=\E(0, sc=\E7, setab=\E[4%p1%dm, setaf=\E[3%p1%dm, sgr=\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t\016%e\017%;, sgr0=\E[m\017, smacs=^N, smcup=\E7\E[?47h, smir=\E[4h, smso=\E[7m, smul=\E[4m, tbc=\E[3g, vpa=\E[%i%p1%dd, dvtm-256color|dynamic virtual terminal manager with 256 colors, use=dvtm, colors#256, pairs#32767, setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, dvtm-0.12/config.mk0000644000175000017500000000073712355724771012656 0ustar marcmarc# dvtm version VERSION = 0.12 # Customize below to fit your system PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man INCS = -I. LIBS = -lc -lutil -lncursesw # NetBSD #LIBS = -lc -lutil -lcurses # AIX #LIBS = -lc -lncursesw # Cygwin #INCS += -I/usr/include/ncurses CFLAGS += -std=c99 -Os ${INCS} -DVERSION=\"${VERSION}\" -DNDEBUG LDFLAGS += ${LIBS} DEBUG_CFLAGS = ${CFLAGS} -UNDEBUG -O0 -g -ggdb -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter CC = cc dvtm-0.12/testsuite.sh0000755000175000017500000000210112355724771013433 0ustar marcmarc#!/bin/sh MOD="" # CTRL+g ESC="" # \e DVTM="./dvtm" LOG="dvtm.log" TEST_LOG="$0.log" UTF8_TEST_URL="http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt" [ ! -z "$1" ] && DVTM="$1" [ ! -x "$DVTM" ] && echo "usage: $0 path-to-dvtm-binary" && exit 1 dvtm_input() { printf "$1" } dvtm_cmd() { printf "${MOD}$1\n" sleep 1 } sh_cmd() { printf "$1\n" sleep 1 } test_copymode() { # requires wget, diff, vi local FILENAME="UTF-8-demo.txt" [ ! -e "$FILENAME" ] && (wget "$UTF8_TEST_URL" -O "$FILENAME" > /dev/null 2>&1 || return 1) sleep 1 sh_cmd "cat $FILENAME" dvtm_cmd 'v' dvtm_input "?UTF-8 encoded\n" dvtm_input '^kvGk$' dvtm_input 'y' rm -f "$FILENAME.copy" sh_cmd "vi $FILENAME.copy" dvtm_input 'i' dvtm_cmd 'p' dvtm_input "${ESC}dd:wq\n" sleep 1 dvtm_cmd 'q' diff -u "$FILENAME" "$FILENAME.copy" 1>&2 local RESULT=$? rm "$FILENAME.copy" return $RESULT } { echo "Testing $DVTM" 1>&2 $DVTM -v 1>&2 test_copymode && echo "copymode: OK" 1>&2 || echo "copymode: FAIL" 1>&2; } 2> "$TEST_LOG" | $DVTM -m ^g 2> $LOG cat "$TEST_LOG" && rm "$TEST_LOG" $LOG dvtm-0.12/grid.c0000644000175000017500000000361112355724771012143 0ustar marcmarcstatic void grid(void) { unsigned int i, m, nm, n, nx, ny, nw, nh, aw, ah, cols, rows; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; /* number of non minimized windows */ nm = n - m; /* grid dimensions */ for (cols = 0; cols <= nm / 2; cols++) if (cols * cols >= nm) break; rows = (cols && (cols - 1) * cols >= nm) ? cols - 1 : cols; /* window geoms (cell height/width) */ nh = (wah - m) / (rows ? rows : 1); nw = waw / (cols ? cols : 1); for (i = 0, c = clients; c; c = c->next, i++) { if (!c->minimized) { /* if there are less clients in the last row than normal adjust the * split rate to fill the empty space */ if (rows > 1 && i == (rows * cols) - cols && (nm - i) <= (nm % cols)) nw = waw / (nm - i); nx = (i % cols) * nw + wax; ny = (i / cols) * nh + way; /* adjust height/width of last row/column's windows */ ah = (i >= cols * (rows - 1)) ? wah - m - nh * rows : 0; /* special case if there are less clients in the last row */ if (rows > 1 && i == nm - 1 && (nm - i) < (nm % cols)) /* (n % cols) == number of clients in the last row */ aw = waw - nw * (nm % cols); else aw = ((i + 1) % cols == 0) ? waw - nw * cols : 0; if (i % cols) { mvvline(ny, nx, ACS_VLINE, nh + ah); /* if we are on the first row, or on the last one and there are fewer clients * than normal whose border does not match the line above, print a top tree char * otherwise a plus sign. */ if (i <= cols || (i >= rows * cols - cols && nm % cols && (cols - (nm % cols)) % 2)) mvaddch(ny, nx, ACS_TTEE); else mvaddch(ny, nx, ACS_PLUS); nx++, aw--; } } else { if (i == nm) { /* first minimized client */ ny = way + wah - m; nx = wax; nw = waw; nh = 1; aw = 0; ah = 0; } else ny++; } resize(c, nx, ny, nw + aw, nh + ah); } } dvtm-0.12/bstack.c0000644000175000017500000000220612355724771012464 0ustar marcmarcstatic void bstack(void) { unsigned int i, m, n, nx, ny, nw, nh, mh, tw; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; if (n == 1) mh = wah; else if (n - 1 == m) mh = wah - m; else mh = screen.mfact * (wah - m); /* true if there are at least 2 non minimized clients */ if (n - 1 > m) tw = waw / (n - m - 1); nx = wax; ny = way; for (i = 0, c = clients; c; c = c->next, i++) { if (i == 0) { /* master */ nh = mh; nw = waw; } else { /* tile window */ if (i == 1) { nx = wax; ny += mh; nh = (way + wah - m) - ny; } if (i == n - m - 1) { /* last not minimized client */ nw = (wax + waw) - nx; } else if (i == n - m) { /* first minimized client */ ny += nh; nx = wax; nw = waw; nh = 1; } else if (c->minimized) { /* minimized window */ nw = waw; nh = 1; ny++; } else /* normal non minimized tile window */ nw = tw; if (i > 1 && !c->minimized) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++, nw--; } } resize(c, nx, ny, nw, nh); if (n > 1 && i < n - m - 1) nx += nw; } } dvtm-0.12/vt.h0000644000175000017500000000614512355724771011661 0ustar marcmarc/* * Copyright © 2004 Bruno T. C. de Oliveira * Copyright © 2006 Pierre Habouzit * Copyright © 2008-2013 Marc André Tanner * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef VT_VT_H #define VT_VT_H #include #include #include #include #include #include #include #ifndef NCURSES_MOUSE_VERSION #define mmask_t unsigned long #endif enum { /* means escape sequence was handled */ VT_ESCSEQ_HANDLER_OK, /* means the escape sequence was not recognized yet, but * there is hope that it still will once more characters * arrive (i.e. it is not yet complete). * * The library will thus continue collecting characters * and calling the handler as each character arrives until * either OK or NOWAY is returned. */ VT_ESCSEQ_HANDLER_NOTYET, /* means the escape sequence was not recognized, and there * is no chance that it will even if more characters are * added to it. */ VT_ESCSEQ_HANDLER_NOWAY }; typedef struct Vt Vt; typedef int (*vt_escseq_handler_t)(Vt *, char *es); enum { VT_EVENT_TITLE, VT_EVENT_COPY_TEXT, }; typedef void (*vt_event_handler_t)(Vt *, int event, void *data); void vt_init(void); void vt_set_keytable(char const * const keytable_overlay[], int count); void vt_shutdown(void); void vt_set_escseq_handler(Vt *, vt_escseq_handler_t); void vt_set_event_handler(Vt *, vt_event_handler_t); void vt_set_data(Vt *, void *); void *vt_get_data(Vt *); void vt_set_default_colors(Vt *, unsigned attrs, short fg, short bg); Vt *vt_create(int rows, int cols, int scroll_buf_sz); void vt_resize(Vt *, int rows, int cols); void vt_destroy(Vt *); pid_t vt_forkpty(Vt *, const char *, const char *argv[], const char *cwd, const char *envp[], int *pty); int vt_getpty(Vt *); unsigned vt_cursor(Vt *t); int vt_process(Vt *); void vt_keypress(Vt *, int keycode); int vt_write(Vt *t, const char *buf, int len); void vt_mouse(Vt *t, int x, int y, mmask_t mask); void vt_dirty(Vt *t); void vt_draw(Vt *, WINDOW *win, int startrow, int startcol); short vt_color_get(Vt *t, short fg, short bg); short vt_color_reserve(short fg, short bg); void vt_scroll(Vt *, int rows); void vt_noscroll(Vt *); void vt_bell(Vt *, bool bell); void vt_togglebell(Vt *); void vt_copymode_enter(Vt *t); void vt_copymode_leave(Vt *t); unsigned vt_copymode(Vt *t); void vt_copymode_keypress(Vt *t, int keycode); #endif /* VT_VT_H */ dvtm-0.12/LICENSE0000644000175000017500000000222212355724771012054 0ustar marcmarcMIT/X Consortium License (c) 2006-2007 Anselm R. Garbe (c) 2007-2012 Marc Andre Tanner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dvtm-0.12/vstack.c0000644000175000017500000000152512355724771012513 0ustar marcmarc/* A vertical stack layout, all windows have the full screen width. */ static void vstack(void) { unsigned int i, m, n, ny, nh, mh, th; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; if (n == 1) mh = wah; else if (n - 1 == m) mh = wah - m; else mh = screen.mfact * (wah - m); /* true if there are at least 2 non minimized clients */ if (n - 1 > m) th = (wah - mh) / (n - 1 - m); /* TODO: place all minimized windows on the last line */ ny = way; for (i = 0, c = clients; c; c = c->next, i++) { if (i == 0) { /* master */ nh = mh; } else if (i == n - m - 1) { /* last not minimized client */ nh = (way + wah) - ny - m; } else if (c->minimized) { nh = 1; } else { /* normal non minimized tile window */ nh = th; } resize(c, wax, ny, waw, nh); ny += nh; } } dvtm-0.12/fibonacci.c0000644000175000017500000000430312355724771013132 0ustar marcmarcstatic void fibonacci(int s) { unsigned int nx, ny, nw, nnw, nh, nnh, i, n, m, nm, mod; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; /* number of non minimized windows */ nm = n - m; /* initial position and dimensions */ nx = wax; ny = way; nw = (n == 1) ? waw : screen.mfact * waw; /* don't waste space dviding by 2 doesn't work for odd numbers * plus we need space for the border too. therefore set up these * variables for the next new width/height */ nnw = waw - nw - 1; nnh = nh = (wah - m); /* leave space for the minimized clients */ /* set the mod factor, 2 for dwindle, 4 for spiral */ mod = s ? 4 : 2; for (i = 0, c = clients; c; c = c->next, i++) { if (!c->minimized) { /* dwindle: even case, spiral: case 0 */ if (i % mod == 0) { if (i) { if (s) { nh = nnh; ny -= nh; } else { ny += nh; nh = nnh; } /* don't adjust the width for the last non-minimized client */ if (i < nm - 1) { nw /= 2; nnw -= nw + 1; } mvaddch(ny, nx - 1, ACS_LTEE); } } else if (i % mod == 1) { /* dwindle: odd case, spiral: case 1 */ nx += nw; mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); ++nx; nw = nnw; /* don't adjust the height for the last non-minimized client */ if (i < nm - 1) { nh /= 2; nnh -= nh; } } else if (i % mod == 2 && s) { /* spiral: case 2 */ ny += nh; nh = nnh; /* don't adjust the width for the last non-minimized client */ if (i < nm - 1) { nw /= 2; nnw -= nw + 1; nx += nnw; mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); ++nx; } else { mvaddch(ny, nx - 1, ACS_LTEE); } } else if (s) { /* spiral: case 3 */ nw = nnw; nx -= nw + 1; /* border */ /* don't adjust the height for the last non-minimized client */ if (i < nm - 1) { nh /= 2; nnh -= nh; ny += nnh; } mvaddch(ny, nx - 1, ACS_LTEE); } } else { nh = 1; nw = waw; nx = wax; ny = way + wah - (n - i); } resize(c, nx, ny, nw, nh); } } static void spiral(void) { fibonacci(1); } static void dwindle(void) { fibonacci(0); } dvtm-0.12/forkpty-aix.c0000644000175000017500000000477312355724771013505 0ustar marcmarc/* * Copyright (c) 2009 Nicholas Marriott * Copyright (c) 2012 Ross Palmer Mohn * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include /* Fatal errors. */ #ifdef NDEBUG #define debug(format, args...) #else #define debug eprint #endif #define fatal(msg) debug("%s: %s", __func__, msg); pid_t forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) { int slave, fd; char *path; pid_t pid; struct termios tio2; if ((*master = open("/dev/ptc", O_RDWR|O_NOCTTY)) == -1) return (-1); if ((path = ttyname(*master)) == NULL) goto out; if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) goto out; switch (pid = fork()) { case -1: goto out; case 0: close(*master); fd = open(_PATH_TTY, O_RDWR|O_NOCTTY); if (fd >= 0) { ioctl(fd, TIOCNOTTY, NULL); close(fd); } if (setsid() < 0) fatal("setsid"); fd = open(_PATH_TTY, O_RDWR|O_NOCTTY); if (fd >= 0) fatal("open succeeded (failed to disconnect)"); fd = open(path, O_RDWR); if (fd < 0) fatal("open failed"); close(fd); fd = open("/dev/tty", O_WRONLY); if (fd < 0) fatal("open failed"); close(fd); if (tcgetattr(slave, &tio2) != 0) fatal("tcgetattr failed"); if (tio != NULL) memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); tio2.c_cc[VERASE] = '\177'; if (tcsetattr(slave, TCSAFLUSH, &tio2) == -1) fatal("tcsetattr failed"); if (ioctl(slave, TIOCSWINSZ, ws) == -1) fatal("ioctl failed"); dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); if (slave > 2) close(slave); return (0); } close(slave); return (pid); out: if (*master != -1) close(*master); if (slave != -1) close(slave); return (-1); } dvtm-0.12/README0000644000175000017500000000220512355724771011730 0ustar marcmarcdvtm - dynamic virtual terminal manager ======================================= dvtm brings dwm and it's concept of tiling window management to the console. See http://www.brain-dump.org/projects/dvtm for the latest version. Requirements ------------ In order to build dvtm you will need: * libncurses or libncursesw for wide character support Installation ------------ Edit config.mk to match your local setup (dvtm is installed into the /usr/local namespace and links against libncursesw by default). Afterwards enter the following command to build and install dvtm (if necessary as root). make && make install Running dvtm ------------ Just run dvtm from the console, redirect stderr to a file (just in case something goes wrong you will see it there). dvtm 2> log If you want to display a one line status bar you can create a named pipe and pass it's name to dvtm via it's -s command line option. Make sure that the pipe remains open until dvtm is closed, see the included dvtm-status script as an example. Configuration ------------- The configuration of dvtm is done by creating a custom config.h and (re)compiling the source code. dvtm-0.12/dvtm.c0000644000175000017500000006774612355724771012213 0ustar marcmarc/* * The initial "port" of dwm to curses was done by * * © 2007-2014 Marc André Tanner * * It is highly inspired by the original X11 dwm and * reuses some code of it which is mostly * * © 2006-2007 Anselm R. Garbe * * See LICENSE for details. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ # include #endif #include "vt.h" #ifdef PDCURSES int ESCDELAY; #endif #ifndef NCURSES_REENTRANT # define set_escdelay(d) (ESCDELAY = (d)) #endif typedef struct { float mfact; int history; int w; int h; bool need_resize; } Screen; typedef struct { const char *symbol; void (*arrange)(void); } Layout; typedef struct Client Client; struct Client { WINDOW *window; Vt *term; const char *cmd; char title[255]; int order; pid_t pid; int pty; unsigned short int id; unsigned short int x; unsigned short int y; unsigned short int w; unsigned short int h; bool has_title_line; bool minimized; bool died; Client *next; Client *prev; }; typedef struct { const char *title; unsigned attrs; short fg; short bg; } ColorRule; #define ALT(k) ((k) + (161 - 'a')) #if defined CTRL && defined _AIX #undef CTRL #endif #ifndef CTRL #define CTRL(k) ((k) & 0x1F) #endif #define CTRL_ALT(k) ((k) + (129 - 'a')) #define MAX_ARGS 3 typedef struct { void (*cmd)(const char *args[]); /* needed to avoid an error about initialization * of nested flexible array members */ const char *args[MAX_ARGS]; } Action; typedef struct { unsigned int mod; unsigned int code; Action action; } Key; typedef struct { mmask_t mask; Action action; } Button; typedef struct { const char *name; Action action; } Cmd; enum { BAR_TOP, BAR_BOTTOM, BAR_OFF }; enum { ALIGN_LEFT, ALIGN_RIGHT }; typedef struct { int fd; int pos; unsigned short int h; unsigned short int y; char text[512]; const char *file; } StatusBar; typedef struct { int fd; const char *file; unsigned short int id; } CmdFifo; #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) #define sstrlen(str) (sizeof(str) - 1) #define max(x, y) ((x) > (y) ? (x) : (y)) #ifdef NDEBUG #define debug(format, args...) #else #define debug eprint #endif /* commands for use by keybindings */ static void create(const char *args[]); static void copymode(const char *args[]); static void focusn(const char *args[]); static void focusnext(const char *args[]); static void focusnextnm(const char *args[]); static void focusprev(const char *args[]); static void focusprevnm(const char *args[]); static void focuslast(const char *args[]); static void killclient(const char *args[]); static void paste(const char *args[]); static void quit(const char *args[]); static void redraw(const char *args[]); static void scrollback(const char *args[]); static void send(const char *args[]); static void setlayout(const char *args[]); static void setmfact(const char *args[]); static void startup(const char *args[]); static void togglebar(const char *args[]); static void togglebell(const char *key[]); static void toggleminimize(const char *args[]); static void togglemouse(const char *args[]); static void togglerunall(const char *args[]); static void zoom(const char *args[]); /* commands for use by mouse bindings */ static void mouse_focus(const char *args[]); static void mouse_fullscreen(const char *args[]); static void mouse_minimize(const char *args[]); static void mouse_zoom(const char *args[]); /* functions and variables available to layouts via config.h */ static void resize(Client *c, int x, int y, int w, int h); extern Screen screen; static unsigned int waw, wah, wax, way; static Client *clients = NULL; static char *title; #define COLOR(fg, bg) COLOR_PAIR(vt_color_reserve(fg, bg)) #define NOMOD ERR #include "config.h" /* global variables */ Screen screen = { MFACT, SCROLL_HISTORY }; static Client *sel = NULL; static Client *lastsel = NULL; static Client *msel = NULL; static bool mouse_events_enabled = ENABLE_MOUSE; static Layout *layout = layouts; static StatusBar bar = { -1, BAR_POS, 1 }; static CmdFifo cmdfifo = { -1 }; static const char *shell; static char *copybuf; static volatile sig_atomic_t running = true; static bool runinall = false; static void eprint(const char *errstr, ...) { va_list ap; va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); } static void error(const char *errstr, ...) { va_list ap; va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); exit(EXIT_FAILURE); } static bool isarrange(void (*func)()) { return func == layout->arrange; } static bool is_content_visible(Client *c) { if (!c) return false; if (isarrange(fullscreen)) return sel == c; return !c->minimized; } static void drawbar() { wchar_t wbuf[sizeof bar.text]; int x, y, w, maxwidth = screen.w - 2; if (bar.pos == BAR_OFF) return; getyx(stdscr, y, x); attrset(BAR_ATTR); mvaddch(bar.y, 0, '['); if (mbstowcs(wbuf, bar.text, sizeof bar.text) == (size_t)-1) return; if ((w = wcswidth(wbuf, maxwidth)) == -1) return; if (BAR_ALIGN == ALIGN_RIGHT) { for (int i = 0; i + w < maxwidth; i++) addch(' '); } addstr(bar.text); if (BAR_ALIGN == ALIGN_LEFT) { for (; w < maxwidth; w++) addch(' '); } mvaddch(bar.y, screen.w - 1, ']'); attrset(NORMAL_ATTR); move(y, x); wnoutrefresh(stdscr); } static int show_border() { return (bar.fd != -1 && bar.pos != BAR_OFF) || (clients && clients->next); } static void draw_border(Client *c) { if (!show_border()) return; char t = '\0'; int x, y, maxlen; wattrset(c->window, (sel == c || (runinall && !c->minimized)) ? SELECTED_ATTR : NORMAL_ATTR); getyx(c->window, y, x); mvwhline(c->window, 0, 0, ACS_HLINE, c->w); maxlen = c->w - (2 + sstrlen(TITLE) - sstrlen("%s%sd") + sstrlen(SEPARATOR) + 2); if (maxlen < 0) maxlen = 0; if ((size_t)maxlen < sizeof(c->title)) { t = c->title[maxlen]; c->title[maxlen] = '\0'; } mvwprintw(c->window, 0, 2, TITLE, *c->title ? c->title : "", *c->title ? SEPARATOR : "", c->order); if (t) c->title[maxlen] = t; wmove(c->window, y, x); } static void draw_content(Client *c) { vt_draw(c->term, c->window, c->has_title_line, 0); } static void draw(Client *c) { if (is_content_visible(c)) { redrawwin(c->window); draw_content(c); } if (!isarrange(fullscreen) || sel == c) draw_border(c); wnoutrefresh(c->window); } static void draw_all() { if (!isarrange(fullscreen)) { for (Client *c = clients; c; c = c->next) { if (c == sel) continue; draw(c); } } /* as a last step the selected window is redrawn, * this has the effect that the cursor position is * accurate */ if (sel) draw(sel); } static void arrange() { erase(); drawbar(); attrset(NORMAL_ATTR); layout->arrange(); wnoutrefresh(stdscr); draw_all(); } static void attach(Client *c) { if (clients) clients->prev = c; c->next = clients; c->prev = NULL; clients = c; for (int o = 1; c; c = c->next, o++) c->order = o; } static void attachafter(Client *c, Client *a) { /* attach c after a */ if (c == a) return; if (!a) for (a = clients; a && a->next; a = a->next); if (a) { if (a->next) a->next->prev = c; c->next = a->next; c->prev = a; a->next = c; for (int o = a->order; c; c = c->next) c->order = ++o; } } static void detach(Client *c) { Client *d; if (c->prev) c->prev->next = c->next; if (c->next) { c->next->prev = c->prev; for (d = c->next; d; d = d->next) --d->order; } if (c == clients) clients = c->next; c->next = c->prev = NULL; } static void settitle(Client *c) { char *term, *t = title; if (!t && sel == c && *c->title) t = c->title; if (t && (term = getenv("TERM")) && !strstr(term, "linux")) printf("\033]0;%s\007", t); } static void focus(Client *c) { Client *tmp = sel; if (sel == c) return; lastsel = sel; sel = c; settitle(c); if (tmp && !isarrange(fullscreen)) { draw_border(tmp); wnoutrefresh(tmp->window); } if (isarrange(fullscreen)) { draw(c); } else { draw_border(c); wnoutrefresh(c->window); } curs_set(!c->minimized && vt_cursor(c->term)); } static void applycolorrules(Client *c) { const ColorRule *r = colorrules; short fg = r->fg, bg = r->bg; unsigned attrs = r->attrs; for (unsigned int i = 1; i < countof(colorrules); i++) { r = &colorrules[i]; if (strstr(c->title, r->title)) { attrs = r->attrs; fg = r->fg; bg = r->bg; break; } } vt_set_default_colors(c->term, attrs, fg, bg); } static void term_event_handler(Vt *term, int event, void *event_data) { Client *c = (Client *)vt_get_data(term); switch (event) { case VT_EVENT_TITLE: if (event_data) strncpy(c->title, event_data, sizeof(c->title) - 1); c->title[event_data ? sizeof(c->title) - 1 : 0] = '\0'; settitle(c); if (!isarrange(fullscreen) || sel == c) draw_border(c); applycolorrules(c); break; case VT_EVENT_COPY_TEXT: if (event_data) { free(copybuf); copybuf = event_data; } break; } } static void move_client(Client *c, int x, int y) { if (c->x == x && c->y == y) return; debug("moving, x: %d y: %d\n", x, y); if (mvwin(c->window, y, x) == ERR) { eprint("error moving, x: %d y: %d\n", x, y); } else { c->x = x; c->y = y; } } static void resize_client(Client *c, int w, int h) { bool has_title_line = show_border(); bool resize_window = c->w != w || c->h != h; if (resize_window) { debug("resizing, w: %d h: %d\n", w, h); if (wresize(c->window, h, w) == ERR) { eprint("error resizing, w: %d h: %d\n", w, h); } else { c->w = w; c->h = h; } } if (resize_window || c->has_title_line != has_title_line) { c->has_title_line = has_title_line; vt_resize(c->term, h - has_title_line, w); } } static void resize(Client *c, int x, int y, int w, int h) { resize_client(c, w, h); move_client(c, x, y); } static Client* get_client_by_pid(pid_t pid) { for (Client *c = clients; c; c = c->next) { if (c->pid == pid) return c; } return NULL; } static Client* get_client_by_coord(unsigned int x, unsigned int y) { if (y < way || y >= wah) return NULL; if (isarrange(fullscreen)) return sel; for (Client *c = clients; c; c = c->next) { if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) { debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order); return c; } } return NULL; } static void sigchld_handler(int sig) { int errsv = errno; int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) != 0) { if (pid == -1) { if (errno == ECHILD) { /* no more child processes */ break; } eprint("waitpid: %s\n", strerror(errno)); break; } debug("child with pid %d died\n", pid); Client *c = get_client_by_pid(pid); if (c) c->died = true; } errno = errsv; } static void sigwinch_handler(int sig) { screen.need_resize = true; } static void sigterm_handler(int sig) { running = false; } static void updatebarpos(void) { bar.y = 0; wax = 0; way = 0; wah = screen.h; if (bar.fd == -1) return; if (bar.pos == BAR_TOP) { wah -= bar.h; way += bar.h; } else if (bar.pos == BAR_BOTTOM) { wah -= bar.h; bar.y = wah; } } static void resize_screen() { struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) == -1) { getmaxyx(stdscr, screen.h, screen.w); } else { screen.w = ws.ws_col; screen.h = ws.ws_row; } debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h); resizeterm(screen.h, screen.w); wresize(stdscr, screen.h, screen.w); waw = screen.w; wah = screen.h; updatebarpos(); clear(); arrange(); } static bool is_modifier(unsigned int mod) { for (unsigned int i = 0; i < countof(keys); i++) { if (keys[i].mod == mod) return true; } return false; } static Key* keybinding(unsigned int mod, unsigned int code) { for (unsigned int i = 0; i < countof(keys); i++) { if (keys[i].mod == mod && keys[i].code == code) return &keys[i]; } return NULL; } static void keypress(int code) { unsigned int len = 1; char buf[8] = { '\e' }; if (code == '\e') { /* pass characters following escape to the underlying app */ nodelay(stdscr, TRUE); for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++) buf[len] = t; nodelay(stdscr, FALSE); } for (Client *c = runinall ? clients : sel; c; c = c->next) { if (is_content_visible(c)) { if (code == '\e') vt_write(c->term, buf, len); else vt_keypress(c->term, code); } if (!runinall) break; } } static void mouse_setup() { #ifdef CONFIG_MOUSE mmask_t mask = 0; if (mouse_events_enabled) { mask = BUTTON1_CLICKED | BUTTON2_CLICKED; for (unsigned int i = 0; i < countof(buttons); i++) mask |= buttons[i].mask; } mousemask(mask, NULL); #endif /* CONFIG_MOUSE */ } static void setup() { if (!(shell = getenv("SHELL"))) shell = "/bin/sh"; setlocale(LC_CTYPE, ""); initscr(); start_color(); noecho(); keypad(stdscr, TRUE); mouse_setup(); raw(); vt_init(); vt_set_keytable(keytable, countof(keytable)); resize_screen(); struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = sigwinch_handler; sigaction(SIGWINCH, &sa, NULL); sa.sa_handler = sigchld_handler; sigaction(SIGCHLD, &sa, NULL); sa.sa_handler = sigterm_handler; sigaction(SIGTERM, &sa, NULL); } static void destroy(Client *c) { if (sel == c) focusnextnm(NULL); detach(c); if (sel == c) { if (clients) { focus(clients); toggleminimize(NULL); } else { sel = NULL; } } if (lastsel == c) lastsel = NULL; werase(c->window); wnoutrefresh(c->window); vt_destroy(c->term); delwin(c->window); if (!clients && countof(actions)) { if (!strcmp(c->cmd, shell)) quit(NULL); else create(NULL); } free(c); arrange(); } static void cleanup() { while (clients) destroy(clients); vt_shutdown(); endwin(); free(copybuf); if (bar.fd > 0) close(bar.fd); if (bar.file) unlink(bar.file); if (cmdfifo.fd > 0) close(cmdfifo.fd); if (cmdfifo.file) unlink(cmdfifo.file); } static char *getcwd_by_pid(Client *c) { if (!c) return NULL; char buf[32]; snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid); return realpath(buf, NULL); } /* commands for use by keybindings */ static void create(const char *args[]) { Client *c = calloc(1, sizeof(Client)); if (!c) return; const char *cmd = (args && args[0]) ? args[0] : shell; const char *pargs[] = { "/bin/sh", "-c", cmd, NULL }; c->id = ++cmdfifo.id; char buf[8], *cwd = NULL; snprintf(buf, sizeof buf, "%d", c->id); const char *env[] = { "DVTM", VERSION, "DVTM_WINDOW_ID", buf, NULL }; if (!(c->window = newwin(wah, waw, way, wax))) { free(c); return; } c->has_title_line = show_border(); if (!(c->term = vt_create(screen.h - c->has_title_line, screen.w, screen.history))) { delwin(c->window); free(c); return; } c->cmd = cmd; if (args && args[1]) { strncpy(c->title, args[1], sizeof(c->title) - 1); c->title[sizeof(c->title) - 1] = '\0'; } if (args && args[2]) cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2]; c->pid = vt_forkpty(c->term, "/bin/sh", pargs, cwd, env, &c->pty); if (args && args[2] && !strcmp(args[2], "$CWD")) free(cwd); vt_set_data(c->term, c); vt_set_event_handler(c->term, term_event_handler); c->w = screen.w; c->h = screen.h; c->x = wax; c->y = way; c->order = 0; c->minimized = false; debug("client with pid %d forked\n", c->pid); attach(c); focus(c); arrange(); } static void copymode(const char *args[]) { if (!sel) return; vt_copymode_enter(sel->term); if (args[0]) { vt_copymode_keypress(sel->term, args[0][0]); draw(sel); } } static void focusn(const char *args[]) { for (Client *c = clients; c; c = c->next) { if (c->order == atoi(args[0])) { focus(c); if (c->minimized) toggleminimize(NULL); return; } } } static void focusnext(const char *args[]) { if (!sel) return; Client *c = sel->next; if (!c) c = clients; if (c) focus(c); } static void focusnextnm(const char *args[]) { if (!sel) return; Client *c = sel; do { c = c->next; if (!c) c = clients; } while (c->minimized && c != sel); focus(c); } static void focusprev(const char *args[]) { if (!sel) return; Client *c = sel->prev; if (!c) for (c = clients; c && c->next; c = c->next); if (c) focus(c); } static void focusprevnm(const char *args[]) { if (!sel) return; Client *c = sel; do { c = c->prev; if (!c) for (c = clients; c && c->next; c = c->next); } while (c->minimized && c != sel); focus(c); } static void focuslast(const char *args[]) { if (lastsel) focus(lastsel); } static void killclient(const char *args[]) { if (!sel) return; debug("killing client with pid: %d\n", sel->pid); kill(-sel->pid, SIGKILL); } static void paste(const char *args[]) { if (sel && copybuf) vt_write(sel->term, copybuf, strlen(copybuf)); } static void quit(const char *args[]) { cleanup(); exit(EXIT_SUCCESS); } static void redraw(const char *args[]) { for (Client *c = clients; c; c = c->next) { if (!c->minimized) { vt_dirty(c->term); wclear(c->window); wnoutrefresh(c->window); } } resize_screen(); } static void scrollback(const char *args[]) { if (!is_content_visible(sel)) return; if (!args[0] || atoi(args[0]) < 0) vt_scroll(sel->term, -sel->h/2); else vt_scroll(sel->term, sel->h/2); draw(sel); curs_set(vt_cursor(sel->term)); } static void send(const char *args[]) { if (sel && args && args[0]) vt_write(sel->term, args[0], strlen(args[0])); } static void setlayout(const char *args[]) { unsigned int i; if (!args || !args[0]) { if (++layout == &layouts[countof(layouts)]) layout = &layouts[0]; } else { for (i = 0; i < countof(layouts); i++) if (!strcmp(args[0], layouts[i].symbol)) break; if (i == countof(layouts)) return; layout = &layouts[i]; } arrange(); } static void setmfact(const char *args[]) { float delta; if (isarrange(fullscreen) || isarrange(grid)) return; /* arg handling, manipulate mfact */ if (args[0] == NULL) { screen.mfact = MFACT; } else if (1 == sscanf(args[0], "%f", &delta)) { if (args[0][0] == '+' || args[0][0] == '-') screen.mfact += delta; else screen.mfact = delta; if (screen.mfact < 0.1) screen.mfact = 0.1; else if (screen.mfact > 0.9) screen.mfact = 0.9; } arrange(); } static void startup(const char *args[]) { for (unsigned int i = 0; i < countof(actions); i++) actions[i].cmd(actions[i].args); } static void togglebar(const char *args[]) { if (bar.pos == BAR_OFF) bar.pos = (BAR_POS == BAR_OFF) ? BAR_TOP : BAR_POS; else bar.pos = BAR_OFF; updatebarpos(); redraw(NULL); } static void togglebell(const char *args[]) { vt_togglebell(sel->term); } static void toggleminimize(const char *args[]) { Client *c, *m; unsigned int n; if (!sel) return; /* the last window can't be minimized */ if (!sel->minimized) { for (n = 0, c = clients; c; c = c->next) if (!c->minimized) n++; if (n == 1) return; } sel->minimized = !sel->minimized; m = sel; /* check whether the master client was minimized */ if (sel == clients && sel->minimized) { c = sel->next; detach(c); attach(c); focus(c); detach(m); for (; c && c->next && !c->next->minimized; c = c->next); attachafter(m, c); } else if (m->minimized) { /* non master window got minimized move it above all other * minimized ones */ focusnextnm(NULL); detach(m); for (c = clients; c && c->next && !c->next->minimized; c = c->next); attachafter(m, c); } else { /* window is no longer minimized, move it to the master area */ vt_dirty(m->term); detach(m); attach(m); } arrange(); } static void togglemouse(const char *args[]) { mouse_events_enabled = !mouse_events_enabled; mouse_setup(); } static void togglerunall(const char *args[]) { runinall = !runinall; draw_all(); } static void zoom(const char *args[]) { Client *c; if (!sel) return; if (args && args[0]) focusn(args); if ((c = sel) == clients) if (!(c = c->next)) return; detach(c); attach(c); focus(c); if (c->minimized) toggleminimize(NULL); arrange(); } /* commands for use by mouse bindings */ static void mouse_focus(const char *args[]) { focus(msel); if (msel->minimized) toggleminimize(NULL); } static void mouse_fullscreen(const char *args[]) { mouse_focus(NULL); if (isarrange(fullscreen)) setlayout(NULL); else setlayout(args); } static void mouse_minimize(const char *args[]) { focus(msel); toggleminimize(NULL); } static void mouse_zoom(const char *args[]) { focus(msel); zoom(NULL); } static Cmd * get_cmd_by_name(const char *name) { for (unsigned int i = 0; i < countof(commands); i++) { if (!strcmp(name, commands[i].name)) return &commands[i]; } return NULL; } static void handle_cmdfifo() { int r; char *p, *s, cmdbuf[512], c; Cmd *cmd; switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) { case -1: case 0: cmdfifo.fd = -1; break; default: cmdbuf[r] = '\0'; p = cmdbuf; while (*p) { /* find the command name */ for (; *p == ' ' || *p == '\n'; p++); for (s = p; *p && *p != ' ' && *p != '\n'; p++); if ((c = *p)) *p++ = '\0'; if (*s && (cmd = get_cmd_by_name(s)) != NULL) { bool quote = false; int argc = 0; /* XXX: initializer assumes MAX_ARGS == 2 use a initialization loop? */ const char *args[MAX_ARGS] = { NULL, NULL, NULL}, *arg; /* if arguments were specified in config.h ignore the one given via * the named pipe and thus skip everything until we find a new line */ if (cmd->action.args[0] || c == '\n') { debug("execute %s", s); cmd->action.cmd(cmd->action.args); while (*p && *p != '\n') p++; continue; } /* no arguments were given in config.h so we parse the command line */ while (*p == ' ') p++; arg = p; for (; (c = *p); p++) { switch (*p) { case '\\': /* remove the escape character '\\' move every * following character to the left by one position */ switch (p[1]) { case '\\': case '\'': case '\"': { char *t = p+1; do { t[-1] = *t; } while (*t++); } } break; case '\'': case '\"': quote = !quote; break; case ' ': if (!quote) { case '\n': /* remove trailing quote if there is one */ if (*(p - 1) == '\'' || *(p - 1) == '\"') *(p - 1) = '\0'; *p++ = '\0'; /* remove leading quote if there is one */ if (*arg == '\'' || *arg == '\"') arg++; if (argc < MAX_ARGS) args[argc++] = arg; while (*p == ' ') ++p; arg = p--; } break; } if (c == '\n' || *p == '\n') { if (!*p) p++; debug("execute %s", s); for(int i = 0; i < argc; i++) debug(" %s", args[i]); debug("\n"); cmd->action.cmd(args); break; } } } } } } static void handle_mouse() { #ifdef CONFIG_MOUSE MEVENT event; unsigned int i; if (getmouse(&event) != OK) return; msel = get_client_by_coord(event.x, event.y); if (!msel) return; debug("mouse x:%d y:%d cx:%d cy:%d mask:%d\n", event.x, event.y, event.x - msel->x, event.y - msel->y, event.bstate); vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate); for (i = 0; i < countof(buttons); i++) { if (event.bstate & buttons[i].mask) buttons[i].action.cmd(buttons[i].action.args); } msel = NULL; #endif /* CONFIG_MOUSE */ } static void handle_statusbar() { char *p; int r; switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) { case -1: strncpy(bar.text, strerror(errno), sizeof bar.text - 1); bar.text[sizeof bar.text - 1] = '\0'; bar.fd = -1; break; case 0: bar.fd = -1; break; default: bar.text[r] = '\0'; p = bar.text + r - 1; for (; p >= bar.text && *p == '\n'; *p-- = '\0'); for (; p >= bar.text && *p != '\n'; --p); if (p >= bar.text) memmove(bar.text, p + 1, strlen(p)); drawbar(); } } static int open_or_create_fifo(const char *name, const char **name_created) { struct stat info; int fd; do { if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) { if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) { *name_created = name; continue; } error("%s\n", strerror(errno)); } } while (fd == -1); if (fstat(fd, &info) == -1) error("%s\n", strerror(errno)); if (!S_ISFIFO(info.st_mode)) error("%s is not a named pipe\n", name); return fd; } static void usage() { cleanup(); eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] " "[-s status-fifo] [-c cmd-fifo] [cmd...]\n"); exit(EXIT_FAILURE); } static bool parse_args(int argc, char *argv[]) { bool init = false; if (!getenv("ESCDELAY")) set_escdelay(100); for (int arg = 1; arg < argc; arg++) { if (argv[arg][0] != '-') { const char *args[] = { argv[arg], NULL, NULL }; if (!init) { setup(); init = true; } create(args); continue; } if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc) usage(); switch (argv[arg][1]) { case 'v': puts("dvtm-"VERSION" © 2007-2014 Marc André Tanner"); exit(EXIT_SUCCESS); case 'M': mouse_events_enabled = !mouse_events_enabled; break; case 'm': { char *mod = argv[++arg]; if (mod[0] == '^' && mod[1]) *mod = CTRL(mod[1]); for (unsigned int i = 0; i < countof(keys); i++) keys[i].mod = *mod; break; } case 'd': set_escdelay(atoi(argv[++arg])); if (ESCDELAY < 50) set_escdelay(50); else if (ESCDELAY > 1000) set_escdelay(1000); break; case 'h': screen.history = atoi(argv[++arg]); break; case 't': title = argv[++arg]; break; case 's': bar.fd = open_or_create_fifo(argv[++arg], &bar.file); updatebarpos(); break; case 'c': { const char *fifo; cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file); if (!(fifo = realpath(argv[arg], NULL))) error("%s\n", strerror(errno)); setenv("DVTM_CMD_FIFO", fifo, 1); break; } default: usage(); } } return init; } int main(int argc, char *argv[]) { int mod = ERR; if (!parse_args(argc, argv)) { setup(); startup(NULL); } while (running) { int r, nfds = 0; fd_set rd; if (screen.need_resize) { resize_screen(); screen.need_resize = false; } FD_ZERO(&rd); FD_SET(STDIN_FILENO, &rd); if (cmdfifo.fd != -1) { FD_SET(cmdfifo.fd, &rd); nfds = cmdfifo.fd; } if (bar.fd != -1) { FD_SET(bar.fd, &rd); nfds = max(nfds, bar.fd); } for (Client *c = clients; c; ) { if (c->died) { Client *t = c->next; destroy(c); c = t; continue; } FD_SET(c->pty, &rd); nfds = max(nfds, c->pty); c = c->next; } doupdate(); r = select(nfds + 1, &rd, NULL, NULL, NULL); if (r == -1 && errno == EINTR) continue; if (r < 0) { perror("select()"); exit(EXIT_FAILURE); } if (FD_ISSET(STDIN_FILENO, &rd)) { int code = getch(); Key *key; if (code >= 0) { if (mod >= 0) { if ((key = keybinding(mod, code))) key->action.cmd(key->action.args); mod = ERR; } else if (code == KEY_MOUSE) { handle_mouse(); } else if (is_modifier(code)) { mod = code; } else if ((key = keybinding(ERR, code))) { key->action.cmd(key->action.args); } else if (sel && vt_copymode(sel->term)) { vt_copymode_keypress(sel->term, code); draw(sel); } else { keypress(code); } } if (r == 1) /* no data available on pty's */ continue; } if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd)) handle_cmdfifo(); if (bar.fd != -1 && FD_ISSET(bar.fd, &rd)) handle_statusbar(); for (Client *c = clients; c; ) { if (FD_ISSET(c->pty, &rd) && !vt_copymode(c->term)) { if (vt_process(c->term) < 0 && errno == EIO) { /* client probably terminated */ Client *t = c->next; destroy(c); c = t; continue; } if (c != sel && is_content_visible(c)) { draw_content(c); wnoutrefresh(c->window); } } c = c->next; } if (is_content_visible(sel)) { draw_content(sel); curs_set(vt_cursor(sel->term)); wnoutrefresh(sel->window); } } cleanup(); return 0; } dvtm-0.12/fullscreen.c0000644000175000017500000000015512355724771013360 0ustar marcmarcstatic void fullscreen(void) { for (Client *c = clients; c; c = c->next) resize(c, wax, way, waw, wah); } dvtm-0.12/tstack.c0000644000175000017500000000217012355724771012506 0ustar marcmarcvoid tstack(void) { unsigned int i, m, n, nx, ny, nw, nh, mh, tw; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; /* relative height */ mh = (wah - m) * (n == 1 || n - 1 == m ? 1 : screen.mfact); /* true if there are at least 2 non minimized clients */ if (n - 1 > m) tw = waw / (n - m - 1); nx = wax, nw = waw; for (i = 0, c = clients; c; c = c->next, i++) { if (i == 0) { /* master */ ny = way + wah - mh; nh = mh; } else { /* tile window */ if (i == 1) { nx = wax; ny = way + m; nh = wah - mh - ny + way; } if (i == n - m - 1) { /* last not minimized client */ nw = (wax + waw) - nx; } else if (i == n - m) { /* first minimized client */ nx = wax; --ny; nh = 1; nw = waw; } else if (c->minimized) { /* minimized window */ --ny; nh = 1; nw = waw; } else /* normal non minimized tile window */ nw = tw; if (i > 1 && !c->minimized) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); ++nx, --nw; } } resize(c, nx, ny, nw, nh); if (n > 1 && i < n - m - 1) nx += nw; } } dvtm-0.12/config.def.h0000644000175000017500000001623012355724771013226 0ustar marcmarc/* valid curses attributes are listed below they can be ORed * * A_NORMAL Normal display (no highlight) * A_STANDOUT Best highlighting mode of the terminal. * A_UNDERLINE Underlining * A_REVERSE Reverse video * A_BLINK Blinking * A_DIM Half bright * A_BOLD Extra bright or bold * A_PROTECT Protected mode * A_INVIS Invisible or blank mode */ #define BLUE (COLORS==256 ? 68 : COLOR_BLUE) /* curses attributes for the currently focused window */ #define SELECTED_ATTR COLOR(BLUE, -1) | A_NORMAL /* curses attributes for normal (not selected) windows */ #define NORMAL_ATTR COLOR(-1, -1) | A_NORMAL /* curses attributes for the status bar */ #define BAR_ATTR COLOR(BLUE, -1) | A_NORMAL /* status bar (command line option -s) position */ #define BAR_POS BAR_TOP /* BAR_BOTTOM, BAR_OFF */ /* determines whether the statusbar text should be right or left aligned */ #define BAR_ALIGN ALIGN_RIGHT /* separator between window title and window number */ #define SEPARATOR " | " /* printf format string for the window title, first %s * is replaced by the title, second %s is replaced by * the SEPARATOR, %d stands for the window number */ #define TITLE "[%s%s#%d]" /* master width factor [0.1 .. 0.9] */ #define MFACT 0.5 /* scroll back buffer size in lines */ #define SCROLL_HISTORY 500 #include "tile.c" #include "grid.c" #include "bstack.c" #include "fullscreen.c" /* by default the first layout entry is used */ static Layout layouts[] = { { "[]=", tile }, { "+++", grid }, { "TTT", bstack }, { "[ ]", fullscreen }, }; #define MOD CTRL('g') /* you can at most specifiy MAX_ARGS (3) number of arguments */ static Key keys[] = { { MOD, 'c', { create, { NULL } } }, { MOD, 'C', { create, { NULL, NULL, "$CWD" } } }, { MOD, 'x', { killclient, { NULL } } }, { MOD, 'j', { focusnext, { NULL } } }, { MOD, 'u', { focusnextnm, { NULL } } }, { MOD, 'i', { focusprevnm, { NULL } } }, { MOD, 'k', { focusprev, { NULL } } }, { MOD, 't', { setlayout, { "[]=" } } }, { MOD, 'g', { setlayout, { "+++" } } }, { MOD, 'b', { setlayout, { "TTT" } } }, { MOD, 'm', { setlayout, { "[ ]" } } }, { MOD, ' ', { setlayout, { NULL } } }, { MOD, 'h', { setmfact, { "-0.05" } } }, { MOD, 'l', { setmfact, { "+0.05" } } }, { MOD, '.', { toggleminimize, { NULL } } }, { MOD, 's', { togglebar, { NULL } } }, { MOD, 'M', { togglemouse, { NULL } } }, { MOD, '\n', { zoom , { NULL } } }, { MOD, '1', { focusn, { "1" } } }, { MOD, '2', { focusn, { "2" } } }, { MOD, '3', { focusn, { "3" } } }, { MOD, '4', { focusn, { "4" } } }, { MOD, '5', { focusn, { "5" } } }, { MOD, '6', { focusn, { "6" } } }, { MOD, '7', { focusn, { "7" } } }, { MOD, '8', { focusn, { "8" } } }, { MOD, '9', { focusn, { "9" } } }, { MOD, '\t', { focuslast, { NULL } } }, { MOD, 'q', { quit, { NULL } } }, { MOD, 'a', { togglerunall, { NULL } } }, { MOD, 'r', { redraw, { NULL } } }, { MOD, 'B', { togglebell, { NULL } } }, { MOD, 'v', { copymode, { NULL } } }, { MOD, '/', { copymode, { "/" } } }, { MOD, '?', { copymode, { "?" } } }, { MOD, 'p', { paste, { NULL } } }, { MOD, KEY_PPAGE, { scrollback, { "-1" } } }, { MOD, KEY_NPAGE, { scrollback, { "1" } } }, { MOD, KEY_F(1), { create, { "man dvtm", "dvtm help" } } }, { MOD, MOD, { send, { (const char []){MOD, 0} } } }, { NOMOD, KEY_SPREVIOUS, { scrollback, { "-1" } } }, { NOMOD, KEY_SNEXT, { scrollback, { "1" } } }, }; static const ColorRule colorrules[] = { { "", A_NORMAL, -1, -1 }, /* default */ #if 0 /* title attrs fgcolor bgcolor */ { "ssh", A_NORMAL, COLOR_BLACK, 224 }, #endif }; /* possible values for the mouse buttons are listed below: * * BUTTON1_PRESSED mouse button 1 down * BUTTON1_RELEASED mouse button 1 up * BUTTON1_CLICKED mouse button 1 clicked * BUTTON1_DOUBLE_CLICKED mouse button 1 double clicked * BUTTON1_TRIPLE_CLICKED mouse button 1 triple clicked * BUTTON2_PRESSED mouse button 2 down * BUTTON2_RELEASED mouse button 2 up * BUTTON2_CLICKED mouse button 2 clicked * BUTTON2_DOUBLE_CLICKED mouse button 2 double clicked * BUTTON2_TRIPLE_CLICKED mouse button 2 triple clicked * BUTTON3_PRESSED mouse button 3 down * BUTTON3_RELEASED mouse button 3 up * BUTTON3_CLICKED mouse button 3 clicked * BUTTON3_DOUBLE_CLICKED mouse button 3 double clicked * BUTTON3_TRIPLE_CLICKED mouse button 3 triple clicked * BUTTON4_PRESSED mouse button 4 down * BUTTON4_RELEASED mouse button 4 up * BUTTON4_CLICKED mouse button 4 clicked * BUTTON4_DOUBLE_CLICKED mouse button 4 double clicked * BUTTON4_TRIPLE_CLICKED mouse button 4 triple clicked * BUTTON_SHIFT shift was down during button state change * BUTTON_CTRL control was down during button state change * BUTTON_ALT alt was down during button state change * ALL_MOUSE_EVENTS report all button state changes * REPORT_MOUSE_POSITION report mouse movement */ #ifdef NCURSES_MOUSE_VERSION # define CONFIG_MOUSE /* compile in mouse support if we build against ncurses */ #endif #define ENABLE_MOUSE true /* whether to enable mouse events by default */ #ifdef CONFIG_MOUSE static Button buttons[] = { { BUTTON1_CLICKED, { mouse_focus, { NULL } } }, { BUTTON1_DOUBLE_CLICKED, { mouse_fullscreen, { "[ ]" } } }, { BUTTON2_CLICKED, { mouse_zoom, { NULL } } }, { BUTTON3_CLICKED, { mouse_minimize, { NULL } } }, }; #endif /* CONFIG_MOUSE */ static Cmd commands[] = { { "create", { create, { NULL } } }, }; /* gets executed when dvtm is started */ static Action actions[] = { { create, { NULL } }, }; static char const * const keytable[] = { /* add your custom key escape sequences */ }; dvtm-0.12/Makefile0000644000175000017500000000345112355724771012514 0ustar marcmarcinclude config.mk SRC += dvtm.c vt.c OBJ = ${SRC:.c=.o} all: clean options dvtm options: @echo dvtm build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" config.h: cp config.def.h config.h .c.o: @echo CC $< @${CC} -c ${CFLAGS} $< ${OBJ}: config.h config.mk dvtm: ${OBJ} @echo CC -o $@ @${CC} -o $@ ${OBJ} ${LDFLAGS} debug: clean @make CFLAGS='${DEBUG_CFLAGS}' clean: @echo cleaning @rm -f dvtm ${OBJ} dvtm-${VERSION}.tar.gz dist: clean @echo creating dist tarball @mkdir -p dvtm-${VERSION} @cp -R LICENSE Makefile README testsuite.sh config.def.h config.mk \ ${SRC} vt.h forkpty-aix.c tile.c bstack.c tstack.c vstack.c \ grid.c fullscreen.c fibonacci.c dvtm-status dvtm.info dvtm.1 \ dvtm-${VERSION} @tar -cf dvtm-${VERSION}.tar dvtm-${VERSION} @gzip dvtm-${VERSION}.tar @rm -rf dvtm-${VERSION} install: dvtm @echo stripping executable @strip -s dvtm @echo installing executable file to ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${PREFIX}/bin @cp -f dvtm ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm @cp -f dvtm-status ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm-status @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @sed "s/VERSION/${VERSION}/g" < dvtm.1 > ${DESTDIR}${MANPREFIX}/man1/dvtm.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dvtm.1 @echo installing terminfo description @tic -o ${DESTDIR}${PREFIX}/share/terminfo -s dvtm.info uninstall: @echo removing executable file from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/dvtm @rm -f ${DESTDIR}${PREFIX}/bin/dvtm-status @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 @rm -f ${DESTDIR}${MANPREFIX}/man1/dvtm.1 .PHONY: all options clean dist install uninstall debug dvtm-0.12/tile.c0000644000175000017500000000220012355724771012144 0ustar marcmarcstatic void tile(void) { unsigned int i, m, n, nx, ny, nw, nh, nm, mw, th; Client *c; for (n = 0, m = 0, c = clients; c; c = c->next, n++) if (c->minimized) m++; nm = n - m; /* window geoms */ mw = (n == 1 || n - 1 == m) ? waw : screen.mfact * waw; /* check if there are at least 2 non minimized clients */ if (n - 1 > m) th = (wah - m) / (nm - 1); nx = wax; ny = way; for (i = 0, c = clients; c; c = c->next, i++) { if (i == 0) { /* master */ nw = mw; nh = (n - 1 > m) ? wah : wah - m; } else { /* tile window */ if (!c->minimized) { if (i == 1) { ny = way; nx += mw; nw = waw - mw; mvvline(ny, nx, ACS_VLINE, wah); mvaddch(ny, nx, ACS_TTEE); nx++, nw--; } /* remainder */ if (m == 0 && i + 1 == n) /* no minimized clients */ nh = (way + wah) - ny; else if (i == nm - 1) /* last not minimized client */ nh = (way + wah - (n - i - 1)) - ny; else nh = th; } else { nh = 1; ny = way + wah - (n - i); } if (i > 1 && nm > 1) mvaddch(ny, nx - 1, ACS_LTEE); } resize(c, nx, ny, nw, nh); if (n > 1 && th != wah) ny += nh; } } dvtm-0.12/dvtm.10000644000175000017500000001314412355724771012110 0ustar marcmarc.TH DVTM 1 dvtm\-VERSION .nh .SH NAME dvtm \- dynamic virtual terminal manager .SH SYNOPSIS .B dvtm .RB [ \-v ] .RB [ \-M ] .RB [ \-m .IR modifier ] .RB [ \-d .IR delay ] .RB [ \-h .IR lines ] .RB [ \-t .IR title ] .RB [ \-s .IR status-fifo ] .RB [ \-c .IR cmd-fifo ] .RI [ command \ ... "" ] .SH DESCRIPTION dvtm is a dynamic tiling window manager for the console. As a console window manager it tries to make it easy to work with multiple console based applications. .SH OPTIONS .TP .B \-v Print version information to standard output and exit. .TP .B \-M Toggle default mouse grabbing upon startup. Use this to allow normal mouse operation under X. .TP .BI \-m \ modifier Set command modifier at runtime. .TP .BI \-d \ delay Set the delay ncurses waits before deciding if a character that might be part of an escape sequence is actually part of an escape sequence. .TP .BI \-h \ lines Set the scrollback history buffer size at runtime. .TP .BI \-t \ title Set a static terminal .I title and don't change it to the one of the currently focused window. .TP .BI \-s \ status-fifo Open or create the named pipe .I status-fifo read its content and display it in the statusbar. See the .I dvtm-status script for an usage example. .TP .BI \-c \ cmd-fifo Open or create the named pipe .I cmd-fifo and look for commands to execute which were defined in .IR config.h . .TP .IR command \ ... Execute .IR command (s), each in a separate window. .SH USAGE .SS Keyboard commands .TP .B Mod Each keybinding begins with Mod which defaults to .BR ^g , but can be changed in .I config.h or with the .B \-m command line option. .TP .B Mod\-c Create a new shell window. .TP .B Mod\-x Close focused window. .TP .B Mod\-l Increases the master area width about 5% (all except grid and fullscreen layout). .TP .B Mod\-h Decreases the master area width about 5% (all except grid and fullscreen layout). .TP .B Mod\-j Focus next window. .TP .B Mod\-k Focus previous window. .TP .BI Mod\- n Focus the .IR n \-th window. .TP .B Mod\-Tab Focus previously selected window. .TP .B Mod\-. Toggle minimization of current window. .TP .B Mod\-u Focus next non minimized window. .TP .B Mod\-i Focus previous non minimized window. .TP .B Mod\-m Maximize current window (change to fullscreen layout). .TP .B Shift\-PageUp .TQ .B Mod\-PageUp Scroll up. .TP .B Shift\-PageDown .TQ .B Mod\-PageDown Scroll down. .TP .B Mod\-Space Toggle between defined layouts (affects all windows). .TP .B Mod\-Enter Zooms/cycles current window to/from master area. .TP .B Mod\-t Change to vertical stack tiling layout. .TP .B Mod\-b Change to bottom stack tiling layout. .TP .B Mod\-g Change to grid layout. .TP .B Mod\-s Show/hide the status bar. .TP .B Mod\-r Redraw whole screen. .TP .B Mod\-a Toggle keyboard multiplexing mode, if activated keypresses are sent to all visible windows. .TP .B Mod\-B Toggle bell (off by default). .TP .B Mod\-M Toggle dvtm mouse grabbing. .TP .B Mod\-v Enter copy mode (see section below for navigation commands). .TP .B Mod\-/ Enter copy mode and start searching forward. .TP .B Mod\-? Enter copy mode and start searching backwards. .TP .B Mod\-p Paste last copied text from copy mode at current cursor position. .TP .B Mod\-F1 Show this manual page. .TP .B Mod\-Mod Send the Mod key. .TP .B Mod\-q Quit dvtm. .SS Mouse commands .TP .B Copy and Paste By default dvtm captures mouse events to provide the actions listed below. Unfortunately this interferes with the standard X copy and paste mechanism. To work around this you need to hold down .B Shift while selecting or pasting text. Alternatively you can disable mouse support at compile time, start dvtm with the .B -M flag or toggle mouse support during runtime with .BR Mod\-M . .TP .B Button1 click Focus window. .TP .B Button1 double click Focus window and toggle maximization. .TP .B Button2 click Zoom/cycle current window to/from master area. .TP .B Button3 click Toggle minimization of current window. .SS Copy mode Copy mode gives easy access to past output. The commands use vi style keybindings and support number prefixes as command multipliers. .TP .B Entering Copy mode can be entered with .BR Mod\-v . .TP .B Navigation Once in, navigation works with vi style keybindings .RB ( h , j , k , l , ^ , $ , g , H , M , L , G ) as well as with the .BR Arrows / Home / End / Page-Down / Page-Up keys. .BR CTRL+u can be used as a synonym for .BR Page-Up and similarly .BR CTRL+d corresponds to a .BR Page-Down . .TP .B Searching Search forward with .B / and backwards with .BR ? . Jump forward to next match with .BR n . Jump backwards to next match with .BR N . .TP .B Selecting To start making a selection press .B v (similar to visual mode in vi). .TP .B Copying To copy the current selection use .BR y . If you haven't made a selection the current line is copied. Add a number prefix to copy n lines starting from the current line. This command leaves the copy mode. .TP .B Pasting The previously copied text can be pasted at the current cursor position with .BR Mod\-p . .TP .B Leaving Copy mode is automatically left upon copying something. To manually exit at any time press either .BR ESC , .BR CTRL+c or .BR q . .SH ENVIRONMENT VARIABLES By default dvtm uses its own terminfo file and therefore sets .BR TERM=dvtm within the client windows. This can be overridden by setting the .BR DVTM_TERM environment variable to a valid terminal name before launching dvtm. .SH EXAMPLE See the .I dvtm-status script as an example of how to display text in the status bar. .SH CUSTOMIZATION dvtm is customized by creating a custom .I config.h and (re)compiling the source code. This keeps it fast, secure and simple. .SH AUTHOR dvtm is written by Marc André Tanner