dvtm-0.15/0000755000175000017500000000000012644170710011041 5ustar marcmarcdvtm-0.15/vt.c0000644000175000017500000013306112644170710011642 0ustar marcmarc/* * Copyright © 2004 Bruno T. C. de Oliveira * Copyright © 2006 Pierre Habouzit * Copyright © 2008-2016 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) || defined(__CYGWIN__) # include #elif defined(__FreeBSD__) || defined(__DragonFly__) # include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include #endif #include "vt.h" #ifdef _AIX # include "forkpty-aix.c" #elif defined __sun # include "forkpty-sunos.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 #if defined _AIX && defined CTRL # undef CTRL #endif #ifndef CTRL # define CTRL(k) ((k) & 0x1F) #endif #define IS_CONTROL(ch) !((ch) & 0xffffff60UL) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define LENGTH(arr) (sizeof(arr) / sizeof((arr)[0])) 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]; typedef struct { wchar_t text; attr_t attr; short fg; short bg; } Cell; typedef struct { Cell *cells; unsigned dirty:1; } Row; /* Buffer holding the current terminal window content (as an array) as well * as the scroll back buffer content (as a circular/ring buffer). * * If new content is added to terminal the view port slides down and the * previously top most line is moved into the scroll back buffer at postion * scroll_index. This index will eventually wrap around and thus overwrite * the oldest lines. * * In the scenerio below a scroll up has been performed. That is 'scroll_above' * lines still lie above the current view port. Further scrolling up will show * them. Similarly 'scroll_below' is the amount of lines below the current * viewport. * * The function buffer_boundary sets the row pointers to the start/end range * of the section delimiting the region before/after the viewport. The functions * buffer_row_{first,last} return the first/last logical row. And * buffer_row_{next,prev} allows to iterate over the logical lines in either * direction. * * scroll back buffer * * scroll_buf->+----------------+-----+ * | | | ^ \ * | before | | | | * current terminal content | viewport | | | | * | | | | * +----------------+-----+\ | | | s > scroll_above * ^ | | i | \ | | i | c | * | | | n | \ | | n | r | * | | v | \ | | v | o | * r | | i | \ | | i | l / * o | viewport | s | >|<- scroll_index | s | l \ * w | | i | / | | i | | * s | | b | / | after | b | s > scroll_below * | | l | / | viewport | l | i | * v | | e | / | | e | z / * +----------------+-----+/ | unused | | e * <- maxcols -> | scroll back | | * <- cols -> | buffer | | | * | | | | * | | | v * roll_buf + scroll_size->+----------------+-----+ * <- maxcols -> * <- cols -> */ typedef struct { Row *lines; /* array of Row pointers of size 'rows' */ Row *curs_row; /* row on which the cursor currently resides */ Row *scroll_buf; /* a ring buffer holding the scroll back content */ Row *scroll_top; /* row in lines where scrolling region starts */ Row *scroll_bot; /* row in lines where scrolling region ends */ bool *tabs; /* a boolean flag for each column whether it is a tab */ int scroll_size; /* maximal capacity of scroll back buffer (in lines) */ int scroll_index; /* current index into the ring buffer */ int scroll_above; /* number of lines above current viewport */ int scroll_below; /* number of lines below current viewport */ int rows, cols; /* current dimension of buffer */ int maxcols; /* allocated cells (maximal cols over time) */ attr_t curattrs, savattrs; /* current and saved attributes for cells */ int curs_col; /* current cursor column (zero based) */ int curs_srow, curs_scol; /* saved cursor row/colmn (zero based) */ short curfg, curbg; /* current fore and background colors */ short savfg, savbg; /* saved colors */ } Buffer; struct Vt { Buffer buffer_normal; /* normal screen buffer */ Buffer buffer_alternate; /* alternate screen buffer */ Buffer *buffer; /* currently active buffer (one of the above) */ attr_t defattrs; /* attributes to use for normal/empty cells */ short deffg, defbg; /* colors to use for back normal/empty cells (white/black) */ int pty; /* master side pty file descriptor */ pid_t pid; /* process id of the process running in this vt */ /* flags */ unsigned seen_input:1; unsigned insert:1; unsigned escaped:1; unsigned curshid:1; unsigned curskeymode:1; unsigned bell:1; unsigned relposmode:1; unsigned mousetrack:1; unsigned graphmode:1; unsigned savgraphmode:1; bool charsets[2]; /* buffers and parsing state */ char rbuf[BUFSIZ]; char ebuf[BUFSIZ]; unsigned int rlen, elen; int srow, scol; /* last known offset to display start row, start column */ char title[256]; /* xterm style window title */ vt_title_handler_t title_handler; /* hook which is called when title changes */ vt_urgent_handler_t urgent_handler; /* hook which is called upon bell */ void *data; /* user supplied data */ }; static const char *keytable[KEY_MAX+1] = { [KEY_ENTER] = "\r", ['\n'] = "\n", /* 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); __attribute__ ((const)) static attr_t build_attrs(attr_t 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) { char buf[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 buffer_clear(Buffer *b) { Cell cell = { .text = L'\0', .attr = A_NORMAL, .fg = -1, .bg = -1, }; for (int i = 0; i < b->rows; i++) { Row *row = b->lines + i; for (int j = 0; j < b->cols; j++) { row->cells[j] = cell; row->dirty = true; } } } static void buffer_free(Buffer *b) { for (int i = 0; i < b->rows; i++) free(b->lines[i].cells); free(b->lines); for (int i = 0; i < b->scroll_size; i++) free(b->scroll_buf[i].cells); free(b->scroll_buf); free(b->tabs); } static void buffer_scroll(Buffer *b, int s) { /* work in screenfuls */ int ssz = b->scroll_bot - b->scroll_top; if (s > ssz) { buffer_scroll(b, ssz); buffer_scroll(b, s - ssz); return; } if (s < -ssz) { buffer_scroll(b, -ssz); buffer_scroll(b, s + ssz); return; } b->scroll_above += s; if (b->scroll_above >= b->scroll_size) b->scroll_above = b->scroll_size; if (s > 0 && b->scroll_size) { for (int i = 0; i < s; i++) { Row tmp = b->scroll_top[i]; b->scroll_top[i] = b->scroll_buf[b->scroll_index]; b->scroll_buf[b->scroll_index] = tmp; b->scroll_index++; if (b->scroll_index == b->scroll_size) b->scroll_index = 0; } } row_roll(b->scroll_top, b->scroll_bot, s); if (s < 0 && b->scroll_size) { for (int i = (-s) - 1; i >= 0; i--) { b->scroll_index--; if (b->scroll_index == -1) b->scroll_index = b->scroll_size - 1; Row tmp = b->scroll_top[i]; b->scroll_top[i] = b->scroll_buf[b->scroll_index]; b->scroll_buf[b->scroll_index] = tmp; b->scroll_top[i].dirty = true; } } } static void buffer_resize(Buffer *b, int rows, int cols) { Row *lines = b->lines; if (b->rows != rows) { if (b->curs_row >= lines + rows) { /* scroll up instead of simply chopping off bottom */ buffer_scroll(b, (b->curs_row - b->lines) - rows + 1); } while (b->rows > rows) { free(lines[b->rows - 1].cells); b->rows--; } lines = realloc(lines, sizeof(Row) * rows); } if (b->maxcols < cols) { for (int row = 0; row < b->rows; row++) { lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols); if (b->cols < cols) row_set(lines + row, b->cols, cols - b->cols, NULL); lines[row].dirty = true; } Row *sbuf = b->scroll_buf; for (int row = 0; row < b->scroll_size; row++) { sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols); if (b->cols < cols) row_set(sbuf + row, b->cols, cols - b->cols, NULL); } b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols); for (int col = b->cols; col < cols; col++) b->tabs[col] = !(col & 7); b->maxcols = cols; b->cols = cols; } else if (b->cols != cols) { for (int row = 0; row < b->rows; row++) lines[row].dirty = true; b->cols = cols; } int deltarows = 0; if (b->rows < rows) { while (b->rows < rows) { lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell)); row_set(lines + b->rows, 0, b->maxcols, b); b->rows++; } /* prepare for backfill */ if (b->curs_row >= b->scroll_bot - 1) { deltarows = b->lines + rows - b->curs_row - 1; if (deltarows > b->scroll_above) deltarows = b->scroll_above; } } b->curs_row += lines - b->lines; b->scroll_top = lines; b->scroll_bot = lines + rows; b->lines = lines; /* perform backfill */ if (deltarows > 0) { buffer_scroll(b, -deltarows); b->curs_row += deltarows; } } static bool buffer_init(Buffer *b, int rows, int cols, int scroll_size) { b->curattrs = A_NORMAL; /* white text over black background */ b->curfg = b->curbg = -1; if (scroll_size < 0) scroll_size = 0; if (scroll_size && !(b->scroll_buf = calloc(scroll_size, sizeof(Row)))) return false; b->scroll_size = scroll_size; buffer_resize(b, rows, cols); return true; } static void buffer_boundry(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) { if (bs) *bs = NULL; if (be) *be = NULL; if (as) *as = NULL; if (ae) *ae = NULL; if (!b->scroll_size) return; if (b->scroll_above) { if (bs) *bs = &b->scroll_buf[(b->scroll_index - b->scroll_above + b->scroll_size) % b->scroll_size]; if (be) *be = &b->scroll_buf[(b->scroll_index-1 + b->scroll_size) % b->scroll_size]; } if (b->scroll_below) { if (as) *as = &b->scroll_buf[b->scroll_index]; if (ae) *ae = &b->scroll_buf[(b->scroll_index + b->scroll_below-1) % b->scroll_size]; } } static Row *buffer_row_first(Buffer *b) { Row *bstart; if (!b->scroll_size || !b->scroll_above) return b->lines; buffer_boundry(b, &bstart, NULL, NULL, NULL); return bstart; } static Row *buffer_row_last(Buffer *b) { Row *aend; if (!b->scroll_size || !b->scroll_below) return b->lines + b->rows - 1; buffer_boundry(b, NULL, NULL, NULL, &aend); return aend; } static Row *buffer_row_next(Buffer *b, Row *row) { Row *before_start, *before_end, *after_start, *after_end; Row *first = b->lines, *last = b->lines + b->rows - 1; if (!row) return NULL; buffer_boundry(b, &before_start, &before_end, &after_start, &after_end); if (row >= first && row < last) return ++row; if (row == last) return after_start; if (row == before_end) return first; if (row == after_end) return NULL; if (row == &b->scroll_buf[b->scroll_size - 1]) return b->scroll_buf; return ++row; } static Row *buffer_row_prev(Buffer *b, Row *row) { Row *before_start, *before_end, *after_start, *after_end; Row *first = b->lines, *last = b->lines + b->rows - 1; if (!row) return NULL; buffer_boundry(b, &before_start, &before_end, &after_start, &after_end); if (row > first && row <= last) return --row; if (row == first) return before_end; if (row == before_start) return NULL; if (row == after_start) return last; if (row == b->scroll_buf) return &b->scroll_buf[b->scroll_size - 1]; return --row; } static void cursor_clamp(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 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; buffer_scroll(b, 1); row_set(b->curs_row, 0, b->cols, b); } static void cursor_save(Vt *t) { Buffer *b = t->buffer; b->curs_srow = b->curs_row - b->lines; b->curs_scol = b->curs_col; } static void cursor_restore(Vt *t) { Buffer *b = t->buffer; b->curs_row = b->lines + b->curs_srow; b->curs_col = b->curs_scol; cursor_clamp(t); } static void attributes_save(Vt *t) { Buffer *b = t->buffer; b->savattrs = b->curattrs; b->savfg = b->curfg; b->savbg = b->curbg; t->savgraphmode = t->graphmode; } static void attributes_restore(Vt *t) { Buffer *b = t->buffer; b->curattrs = b->savattrs; b->curfg = b->savfg; b->curbg = b->savbg; t->graphmode = t->savgraphmode; } 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 2: b->curattrs |= A_DIM; break; #ifdef A_ITALIC case 3: b->curattrs |= A_ITALIC; break; #endif 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 | A_DIM); break; #ifdef A_ITALIC case 23: b->curattrs &= ~A_ITALIC; break; #endif 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; attributes_save(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); attributes_restore(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; } cursor_clamp(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 = n - 1; break; case 'd': b->curs_row = b->lines + n - 1; break; } cursor_clamp(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 1049: /* combine 1047 + 1048 */ case 47: /* use alternate/normal screen buffer */ case 1047: if (!set) buffer_clear(&t->buffer_alternate); t->buffer = set ? &t->buffer_alternate : &t->buffer_normal; vt_dirty(t); if (param[i] != 1049) break; /* fall through */ case 1048: /* save/restore cursor */ if (set) cursor_save(t); else cursor_restore(t); break; case 1000: /* enable/disable normal mouse tracking */ t->mousetrack = set; break; } } } static void interpret_csi(Vt *t) { Buffer *b = t->buffer; int csiparam[16]; unsigned 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 >= LENGTH(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 (param_count ? csiparam[0] : 0) { case 0: b->tabs[b->curs_col] = false; break; case 3: memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols); break; } break; case 'r': /* set scrolling region */ interpret_csi_decstbm(t, csiparam, param_count); break; case 's': /* save cursor location */ cursor_save(t); break; case 'u': /* restore cursor location */ cursor_restore(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 an 'operating system command' (OSC) sequence */ static void interpret_osc(Vt *t) { /* ESC ] command ; data BEL * ESC ] command ; data ESC \\ * Note that BEL or ESC \\ have already been replaced with NUL. */ char *data = NULL; int command = strtoul(t->ebuf + 1, &data, 10); if (data && *data == ';') { switch (command) { case 0: /* icon name and window title */ case 2: /* window title */ if (t->title_handler) t->title_handler(t, data+1); break; case 1: /* icon name */ break; default: #ifndef NDEBUG fprintf(stderr, "unknown OSC command: %d\n", command); #endif break; } } } static void try_interpret_escape_seq(Vt *t) { char lastchar = t->ebuf[t->elen - 1]; if (!*t->ebuf) return; switch (*t->ebuf) { case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */ if (t->elen == 2) { if (lastchar == '8') { /* DECALN */ interpret_csi_ed(t, (int []){ 2 }, 1); goto handled; } goto cancel; } break; case '(': case ')': if (t->elen == 2) { interpret_csi_scs(t); goto handled; } break; case ']': /* OSC - operating system command */ if (lastchar == '\a' || (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) { t->elen -= lastchar == '\a' ? 1 : 2; t->ebuf[t->elen] = '\0'; interpret_osc(t); goto handled; } break; case '[': /* CSI - control sequence introducer */ if (is_valid_csi_ender(lastchar)) { interpret_csi(t); goto handled; } break; case '7': /* DECSC: save cursor and attributes */ attributes_save(t); cursor_save(t); goto handled; case '8': /* DECRC: restore cursor and attributes */ attributes_restore(t); cursor_restore(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; for (int col = b->curs_col + direction; count; col += direction) { 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; } } } 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->urgent_handler) t->urgent_handler(t); 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->pid, 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_default_colors_set(Vt *t, attr_t attrs, short fg, short bg) { t->defattrs = attrs; t->deffg = fg; t->defbg = bg; } Vt *vt_create(int rows, int cols, int scroll_size) { if (rows <= 0 || cols <= 0) return NULL; Vt *t = calloc(1, sizeof(Vt)); if (!t) return NULL; t->pty = -1; t->deffg = t->defbg = -1; t->buffer = &t->buffer_normal; if (!buffer_init(&t->buffer_normal, rows, cols, scroll_size) || !buffer_init(&t->buffer_alternate, rows, cols, 0)) { free(t); return NULL; } return t; } void vt_resize(Vt *t, int rows, int cols) { struct winsize ws = { .ws_row = rows, .ws_col = cols }; if (rows <= 0 || cols <= 0) return; vt_noscroll(t); buffer_resize(&t->buffer_normal, rows, cols); buffer_resize(&t->buffer_alternate, rows, cols); cursor_clamp(t); ioctl(t->pty, TIOCSWINSZ, &ws); kill(-t->pid, SIGWINCH); } void vt_destroy(Vt *t) { if (!t) return; buffer_free(&t->buffer_normal); buffer_free(&t->buffer_alternate); close(t->pty); free(t); } void vt_dirty(Vt *t) { Buffer *b = t->buffer; for (Row *row = b->lines, *end = row + b->rows; row < end; row++) row->dirty = true; } void vt_draw(Vt *t, WINDOW *win, int srow, int scol) { Buffer *b = t->buffer; if (srow != t->srow || scol != t->scol) { vt_dirty(t); t->srow = srow; t->scol = scol; } 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, cell->attr << NCURSES_ATTR_SHIFT); wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); } 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); } void vt_scroll(Vt *t, int rows) { Buffer *b = t->buffer; if (!b->scroll_size) return; if (rows < 0) { /* scroll back */ if (rows < -b->scroll_above) rows = -b->scroll_above; } else { /* scroll forward */ if (rows > b->scroll_below) rows = b->scroll_below; } buffer_scroll(b, rows); b->scroll_below -= rows; } void vt_noscroll(Vt *t) { int scroll_below = t->buffer->scroll_below; if (scroll_below) vt_scroll(t, scroll_below); } pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from) { int vt2ed[2], ed2vt[2]; struct winsize ws; ws.ws_row = t->buffer->rows; ws.ws_col = t->buffer->cols; ws.ws_xpixel = ws.ws_ypixel = 0; if (to && pipe(vt2ed)) { *to = -1; to = NULL; } if (from && pipe(ed2vt)) { *from = -1; from = NULL; } pid_t pid = forkpty(&t->pty, NULL, NULL, &ws); if (pid < 0) return -1; if (pid == 0) { setsid(); sigset_t emptyset; sigemptyset(&emptyset); sigprocmask(SIG_SETMASK, &emptyset, NULL); if (to) { close(vt2ed[1]); dup2(vt2ed[0], STDIN_FILENO); close(vt2ed[0]); } if (from) { close(ed2vt[0]); dup2(ed2vt[1], STDOUT_FILENO); close(ed2vt[1]); } int maxfd = sysconf(_SC_OPEN_MAX); for (int fd = 3; fd < maxfd; fd++) if (close(fd) == -1 && errno == EBADF) break; for (const char **envp = env; envp && envp[0]; envp += 2) setenv(envp[0], envp[1], 1); setenv("TERM", vt_term, 1); if (cwd) chdir(cwd); execvp(p, (char *const *)argv); fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]); exit(1); } if (to) { close(vt2ed[0]); *to = vt2ed[1]; } if (from) { close(ed2vt[1]); *from = ed2vt[0]; } return t->pid = pid; } int vt_pty_get(Vt *t) { return t->pty; } ssize_t vt_write(Vt *t, const char *buf, size_t len) { ssize_t ret = len; while (len > 0) { ssize_t res = write(t->pty, buf, len); if (res < 0) { if (errno != EAGAIN && errno != EINTR) return -1; continue; } buf += res; len -= res; } return ret; } static void 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 } } 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 + 1, 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 *term = getenv("DVTM_TERM"); if (!term) term = "dvtm"; snprintf(vt_term, sizeof vt_term, "%s%s", term, COLORS >= 256 ? "-256color" : ""); } void vt_keytable_set(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_title_handler_set(Vt *t, vt_title_handler_t handler) { t->title_handler = handler; } void vt_urgent_handler_set(Vt *t, vt_urgent_handler_t handler) { t->urgent_handler = handler; } void vt_data_set(Vt *t, void *data) { t->data = data; } void *vt_data_get(Vt *t) { return t->data; } bool vt_cursor_visible(Vt *t) { return t->buffer->scroll_below ? false : !t->curshid; } pid_t vt_pid_get(Vt *t) { return t->pid; } size_t vt_content_get(Vt *t, char **buf, bool colored) { Buffer *b = t->buffer; int lines = b->scroll_above + b->scroll_below + b->rows + 1; size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX)); mbstate_t ps; memset(&ps, 0, sizeof(ps)); if (!(*buf = malloc(size))) return 0; char *s = *buf; Cell *prev_cell = NULL; for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) { size_t len = 0; char *last_non_space = s; for (int col = 0; col < b->cols; col++) { Cell *cell = row->cells + col; if (colored) { int esclen = 0; if (!prev_cell || cell->attr != prev_cell->attr) { attr_t attr = cell->attr << NCURSES_ATTR_SHIFT; esclen = sprintf(s, "\033[0%s%s%s%s%s%sm", attr & A_BOLD ? ";1" : "", attr & A_DIM ? ";2" : "", attr & A_UNDERLINE ? ";4" : "", attr & A_BLINK ? ";5" : "", attr & A_REVERSE ? ";7" : "", attr & A_INVIS ? ";8" : ""); if (esclen > 0) s += esclen; } if (!prev_cell || cell->fg != prev_cell->fg || cell->attr != prev_cell->attr) { if (cell->fg == -1) esclen = sprintf(s, "\033[39m"); else esclen = sprintf(s, "\033[38;5;%dm", cell->fg); if (esclen > 0) s += esclen; } if (!prev_cell || cell->bg != prev_cell->bg || cell->attr != prev_cell->attr) { if (cell->bg == -1) esclen = sprintf(s, "\033[49m"); else esclen = sprintf(s, "\033[48;5;%dm", cell->bg); if (esclen > 0) s += esclen; } prev_cell = cell; } if (cell->text) { len = wcrtomb(s, cell->text, &ps); if (len > 0) s += len; last_non_space = s; } else if (len) { len = 0; } else { *s++ = ' '; } } s = last_non_space; *s++ = '\n'; } return s - *buf; } int vt_content_start(Vt *t) { return t->buffer->scroll_above; } dvtm-0.15/dvtm-status0000755000175000017500000000037712644170710013271 0ustar marcmarc#!/bin/sh FIFO="/tmp/dvtm-status.$$" [ -p "$FIFO" ] || mkfifo -m 600 "$FIFO" || exit 1 while true; do date +%H:%M sleep 60 done > "$FIFO" & STATUS_PID=$! dvtm -s "$FIFO" "$@" 2> /dev/null kill $STATUS_PID wait $STATUS_PID 2> /dev/null rm -f "$FIFO" dvtm-0.15/dvtm.info0000644000175000017500000000423012644170710012667 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, ritm=\E[23m, 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, sitm=\E[3m, 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.15/config.mk0000644000175000017500000000110612644170710012635 0ustar marcmarc# dvtm version VERSION = 0.15 # Customize below to fit your system PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man # specify your systems terminfo directory # leave empty to install into your home folder TERMINFO := ${DESTDIR}${PREFIX}/share/terminfo INCS = -I. LIBS = -lc -lutil -lncursesw CPPFLAGS = -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -D_XOPEN_SOURCE_EXTENDED CFLAGS += -std=c99 ${INCS} -DVERSION=\"${VERSION}\" -DNDEBUG ${CPPFLAGS} LDFLAGS += ${LIBS} DEBUG_CFLAGS = ${CFLAGS} -UNDEBUG -O0 -g -ggdb -Wall -Wextra -Wno-unused-parameter CC ?= cc STRIP ?= strip dvtm-0.15/testsuite.sh0000755000175000017500000000234312644170710013433 0ustar marcmarc#!/bin/sh MOD="" # CTRL+g ESC="" # \e DVTM="./dvtm" DVTM_EDITOR="vis" 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, vis local FILENAME="UTF-8-demo.txt" local COPY="$FILENAME.copy" [ ! -e "$FILENAME" ] && (wget "$UTF8_TEST_URL" -O "$FILENAME" > /dev/null 2>&1 || return 1) sleep 1 sh_cmd "cat $FILENAME" dvtm_cmd 'e' dvtm_input "?UTF-8 encoded\n" dvtm_input '^kvG1k$' dvtm_input ":wq\n" sleep 1 rm -f "$COPY" sh_cmd "vis $COPY" dvtm_input 'i' dvtm_cmd 'p' dvtm_input "dd:wq\n" while [ ! -r "$COPY" ]; do sleep 1; done; dvtm_input "exit\n" diff -u "$FILENAME" "$COPY" 1>&2 local RESULT=$? rm -f "$COPY" return $RESULT } if ! which vis > /dev/null 2>&1 ; then echo "vis not found, skiping copymode test" exit 0 fi { 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.15/grid.c0000644000175000017500000000322712644170710012136 0ustar marcmarcstatic void grid(void) { unsigned int i, n, nx, ny, nw, nh, aw, ah, cols, rows; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; /* grid dimensions */ for (cols = 0; cols <= n / 2; cols++) if (cols * cols >= n) break; rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; /* window geoms (cell height/width) */ nh = wah / (rows ? rows : 1); nw = waw / (cols ? cols : 1); for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; /* 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 && (n - i) <= (n % cols)) nw = waw / (n - 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 - nh * rows : 0; /* special case if there are less clients in the last row */ if (rows > 1 && i == n - 1 && (n - i) < (n % cols)) /* (n % cols) == number of clients in the last row */ aw = waw - nw * (n % 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 && n % cols && (cols - (n % cols)) % 2)) mvaddch(ny, nx, ACS_TTEE); else mvaddch(ny, nx, ACS_PLUS); nx++, aw--; } resize(c, nx, ny, nw + aw, nh + ah); i++; } } dvtm-0.15/bstack.c0000644000175000017500000000217712644170710012463 0ustar marcmarcstatic void bstack(void) { unsigned int i, n, nx, ny, nw, nh, m, mw, mh, tw; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; m = MAX(1, MIN(n, screen.nmaster)); mh = n == m ? wah : screen.mfact * wah; mw = waw / m; tw = n == m ? 0 : waw / (n - m); nx = wax; ny = way; for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; if (i < m) { /* master */ if (i > 0) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++; } nh = mh; nw = (i < m - 1) ? mw : (wax + waw) - nx; } else { /* tile window */ if (i == m) { nx = wax; ny += mh; nh = (way + wah) - ny; } if (i > m) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++; } nw = (i < n - 1) ? tw : (wax + waw) - nx; } resize(c, nx, ny, nw, nh); nx += nw; i++; } /* Fill in nmaster intersections */ if (n > m) { nx = wax; for (i = 0; i < m; i++) { if (i > 0) { mvaddch(ny, nx, ACS_PLUS); nx++; } nw = (i < m - 1) ? mw : (wax + waw) - nx; nx += nw; } } } dvtm-0.15/vt.h0000644000175000017500000000435712644170710011654 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_H #define VT_H #include #include #include #ifndef NCURSES_MOUSE_VERSION #define mmask_t unsigned long #endif typedef struct Vt Vt; typedef void (*vt_title_handler_t)(Vt*, const char *title); typedef void (*vt_urgent_handler_t)(Vt*); void vt_init(void); void vt_shutdown(void); void vt_keytable_set(char const * const keytable_overlay[], int count); void vt_default_colors_set(Vt*, attr_t attrs, short fg, short bg); void vt_title_handler_set(Vt*, vt_title_handler_t); void vt_urgent_handler_set(Vt*, vt_urgent_handler_t); void vt_data_set(Vt*, void *); void *vt_data_get(Vt*); 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 *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from); int vt_pty_get(Vt*); bool vt_cursor_visible(Vt*); int vt_process(Vt *); void vt_keypress(Vt *, int keycode); ssize_t vt_write(Vt*, const char *buf, size_t len); void vt_mouse(Vt*, int x, int y, mmask_t mask); void vt_dirty(Vt*); void vt_draw(Vt*, WINDOW *win, int startrow, int startcol); short vt_color_get(Vt*, short fg, short bg); short vt_color_reserve(short fg, short bg); void vt_scroll(Vt*, int rows); void vt_noscroll(Vt*); pid_t vt_pid_get(Vt*); size_t vt_content_get(Vt*, char **s, bool colored); int vt_content_start(Vt*); #endif /* VT_H */ dvtm-0.15/forkpty-sunos.c0000644000175000017500000000407512644170710014056 0ustar marcmarc/* * Copyright (c) 2008 Nicholas Marriott * * 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 #ifndef TTY_NAME_MAX #define TTY_NAME_MAX TTYNAME_MAX #endif pid_t forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) { int slave; char *path; pid_t pid; if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1) return -1; if (grantpt(*master) != 0) goto out; if (unlockpt(*master) != 0) goto out; if ((path = ptsname(*master)) == NULL) goto out; if (name != NULL) strlcpy(name, path, TTY_NAME_MAX); if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) goto out; switch (pid = fork()) { case -1: goto out; case 0: close(*master); setsid(); #ifdef TIOCSCTTY if (ioctl(slave, TIOCSCTTY, NULL) == -1) return -1; #endif if (ioctl(slave, I_PUSH, "ptem") == -1) return -1; if (ioctl(slave, I_PUSH, "ldterm") == -1) return -1; if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1) return -1; if (ioctl(slave, TIOCSWINSZ, ws) == -1) return -1; 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.15/README.md0000644000175000017500000003172212644170710012325 0ustar marcmarc# dvtm - dynamic virtual terminal manager [dvtm](http://www.brain-dump.org/projects/dvtm/) brings the concept of tiling window management, popularized by X11-window managers like [dwm](http://dwm.suckless.org) to the console. As a console window manager it tries to make it easy to work with multiple console based programs. ![abduco+dvtm demo](https://raw.githubusercontent.com/martanne/dvtm/gh-pages/screencast.gif) ## News - [dvtm-0.14](http://www.brain-dump.org/projects/dvtm/dvtm-0.14.tar.gz) [released](http://lists.suckless.org/dev/1502/25558.html) (19.02.2015) - [dvtm-0.13](http://www.brain-dump.org/projects/dvtm/dvtm-0.13.tar.gz) [released](http://lists.suckless.org/dev/1411/24449.html) (15.11.2014) - [dvtm-0.12](http://www.brain-dump.org/projects/dvtm/dvtm-0.12.tar.gz) [released](http://lists.suckless.org/dev/1407/22702.html) (05.07.2014) - [dvtm-0.11](http://www.brain-dump.org/projects/dvtm/dvtm-0.11.tar.gz) [released](http://lists.suckless.org/dev/1403/20371.html) (08.03.2014) - [dvtm-0.10](http://www.brain-dump.org/projects/dvtm/dvtm-0.10.tar.gz) [released](http://lists.suckless.org/dev/1312/18805.html) (28.12.2013) - [dvtm-0.9](http://www.brain-dump.org/projects/dvtm/dvtm-0.9.tar.gz) [released](http://lists.suckless.org/dev/1304/15112.html) (3.04.2013) - [dvtm-0.8](http://www.brain-dump.org/projects/dvtm/dvtm-0.8.tar.gz) [released](http://lists.suckless.org/dev/1208/12004.html) (1.08.2012) - [dvtm-0.7](http://www.brain-dump.org/projects/dvtm/dvtm-0.7.tar.gz) [released](http://lists.suckless.org/dev/1109/9266.html) (4.09.2011) - [dvtm-0.6](http://www.brain-dump.org/projects/dvtm/dvtm-0.6.tar.gz) [released](http://lists.suckless.org/dev/1010/6146.html) (8.10.2010) - [dvtm-0.5.2](http://www.brain-dump.org/projects/dvtm/dvtm-0.5.2.tar.gz) [released](http://lists.suckless.org/dev/0907/0520.html) (7.07.2009) - [dvtm-0.5.1](http://www.brain-dump.org/projects/dvtm/dvtm-0.5.1.tar.gz) [released](http://lists.suckless.org/dwm/0902/7405.html) (8.02.2009) - [dvtm-0.5](http://www.brain-dump.org/projects/dvtm/dvtm-0.5.tar.gz) [released](http://lists.suckless.org/dwm/0901/7354.html) (26.01.2009) - [dvtm-0.4.1](http://www.brain-dump.org/projects/dvtm/dvtm-0.4.1.tar.gz) [released](http://lists.suckless.org/dwm/0805/5672.html) (10.05.2008) - [dvtm-0.4](http://www.brain-dump.org/projects/dvtm/dvtm-0.4.tar.gz) [released](http://lists.suckless.org/dwm/0802/4850.html) (17.02.2008) - [dvtm-0.3](http://www.brain-dump.org/projects/dvtm/dvtm-0.3.tar.gz) [released](http://lists.suckless.org/dwm/0801/4735.html) (12.01.2008) - [dvtm-0.2](http://www.brain-dump.org/projects/dvtm/dvtm-0.2.tar.gz) [released](http://lists.suckless.org/dwm/0712/4677.html) (29.12.2007) - [dvtm-0.1](http://www.brain-dump.org/projects/dvtm/dvtm-0.1.tar.gz) [released](http://lists.suckless.org/dwm/0712/4632.html) (21.12.2007) - [dvtm-0.01](http://www.brain-dump.org/projects/dvtm/dvtm-0.01.tar.gz) [released](http://lists.suckless.org/dwm/0712/4424.html) (08.12.2007) ## Download Either Download the latest source tarball [dvtm-0.14.tar.gz](http://www.brain-dump.org/projects/dvtm/dvtm-0.14.tar.gz) with sha1sum 205a2165e70455309f7ed6a6f11b3072fb9b13c3 dvtm-0.14.tar.gz compile (you will need curses headers) and install it $EDITOR config.mk && $EDITOR config.def.h && make && sudo make install or use one of the distribution provided packages: * [Debian](http://packages.debian.org/dvtm) * [Ubuntu](http://packages.ubuntu.com/dvtm) * [Fedora](https://admin.fedoraproject.org/pkgdb/package/dvtm/) * [openSUSE](http://software.opensuse.org/package/dvtm?search_term=dvtm) * [ArchLinux](http://www.archlinux.org/packages/?q=dvtm) * [Gentoo](http://packages.gentoo.org/package/app-misc/dvtm) * [Slackware](http://slackbuilds.org/result/?search=dvtm) * [FreeBSD](http://www.freshports.org/sysutils/dvtm/) * [NetBSD](http://www.pkgsrc.se/misc/dvtm/) * [OpenBSD](http://openports.se/misc/dvtm) * [Mac OS X](http://braumeister.org/formula/dvtm) via homebrew ## Why dvtm? The philosophy behind dvtm strives to adhere to the [Unix philosophy](http://www.catb.org/esr/writings/taoup/html/ch01s06.html). It tries to do one thing, *dynamic* window management on the console, and to do it well. As such dvtm does *not* implement [session management](#faq) but instead delegates this task to a separate tool called [abduco](http://www.brain-dump.org/projects/abduco/). Similarly dvtm's copy mode is implemented by piping the scroll back buffer content to an external editor and only storing whatever the editor writes to `stdout`. Hence the selection process is delegated to the editor where powerful features such as regular expression search are available. As a result dvtm's source code is relatively small ([~4000 lines of C](http://www.ohloh.net/p/dvtm/analyses/latest/languages_summary)), simple and therefore easy to hack on. ## Quickstart All of dvtm keybindings start with a common modifier which from now on is refered to as `MOD`. By default `MOD` is set to `CTRL+g` however this can be changed at runttime with the `-m` command line option. For example setting `MOD` to `CTRL-b` is accomplished by starting `dvtm -m ^b`. ### Windows New windows are created with `MOD+c` and closed with `MOD+x`. To switch among the windows use `MOD+j` and `MOD+k` or `MOD+[1..9]` where the digit corresponds to the window number which is displayed in the title bar. Windows can be minimized and restored with `MOD+.`. Input can be directed to all visible window by pressing `MOD+a`, issuing the same key combination again restores normal behaviour i.e. only the currently focused window will receive input. ### Layouts Visible Windows are arranged by a layout. Each layout consists of a master and a tile area. Typically the master area occupies the largest part of the screen and is intended for the currently most important window. The size of the master area can be shrunk with `MOD+h` and enlarged with `MOD-l` respectively. Windows can be zoomed into the master area with `MOD+Enter`. The number of windows in the master area can be increased and decreased with `MOD+i` and `MOD+d`. By default dvtm comes with 4 different layouts which can be cycled through via `MOD+Space` * vertical stack: master area on the left half, other clients stacked on the right * bottom stack: master area on the top half, other clients stacked below * grid: every window gets an equally sized portion of the screen * fullscreen: only the selected window is shown and occupies the whole available display area `MOD+m` Further layouts are included in the source tarball but disabled by default. ### Tagging Each window has a non empty set of tags [1..n] associated with it. A view consists of a number of tags. The current view includes all windows which are tagged with the currently active tags. The following key bindings are used to manipulate the tagsets. - `MOD-0` view all windows with any tag - `Mod-v-Tab` toggles to the previously selected tags - `MOD-v-[1..n]` view all windows with nth tag - `Mod-V-[1..n]` add/remove all windows with nth tag to/from the view - `Mod-t-[1..n]` apply nth tag to focused window - `Mod-T-[1..n]` add/remove nth tag to/from focused window ### Statusbar dvtm can be instructed to read and display status messages from a named pipe. As an example the `dvtm-status` script is provided which shows the current time. #!/bin/sh FIFO="/tmp/dvtm-status.$$" [ -p "$FIFO" ] || mkfifo -m 600 "$FIFO" || exit 1 while true; do date +%H:%M sleep 60 done > "$FIFO" & STATUS_PID=$! dvtm -s "$FIFO" "$@" 2> /dev/null kill $STATUS_PID wait $STATUS_PID 2> /dev/null rm -f "$FIFO" ### Copymode ### `MOD+e` pipes the whole scroll buffer content to an external editor. What ever the editor writes to `stdout` is remembered by dvtm and can later be pasted with `MOD+p`. In order for this to work the editor needs to be usable as a filter and should use `stderr` for its user interface. Examples where this is the case include [sandy](http://tools.suckless.org/sandy) and [vis](http://github.com/martanne/vis). $ echo Hello World | vis - | cat ## Patches There exist a number of out of tree patches which customize dvtm's behaviour: - [pertag](http://waxandwane.org/dvtm.html) (see also the corresponding [mailing list post](http://lists.suckless.org/hackers/1510/8186.html)) ## FAQ ### Detach / reattach functionality dvtm doesn't have session support built in. Use [abduco](http://www.brain-dump.org/projects/abduco/) instead. $ abduco -c dvtm-session Detach using `CTRL-\` and later reattach with $ abduco -a dvtm-session ### Copy / Paste does not work under X If you have mouse support enabled, which is the case with the default settings, you need to hold down shift while selecting and inserting text. In case you don't like this behaviour either run dvtm with the `-M` command line argument, disable it at run time with `MOD+M` or modify `config.def.h` to disable it completely at compile time. You will however no longer be able to perform other mouse actions like selecting windows etc. ### How to change the key bindings? The configuration of dvtm is done by creating a custom `config.h` and (re)compiling the source code. See the default `config.def.h` as an example, adapting it to your preference should be straightforward. You basically define a set of layouts and keys which dvtm will use. There are some pre defined macros to ease configuration. ### WARNING: terminal is not fully functional This means you haven't installed the `dvtm.info` terminfo description which can be done with `tic -s dvtm.info`. If for some reason you can't install new terminfo descriptions set the `DVTM_TERM` environment variable to a known terminal when starting `dvtm` as in $ DVTM_TERM=rxvt dvtm This will instruct dvtm to use rxvt as `$TERM` value within its windows. ### How to set the window title? The window title can be changed by means of a [xterm extension](http://tldp.org/HOWTO/Xterm-Title-3.html#ss3.2) terminal escape sequence $ echo -ne "\033]0;Your title here\007" So for example in `bash` if you want to display the current working directory in the window title this can be accomplished by means of the following section in your startup files. # If this is an xterm set the title to user@host:dir case "$TERM" in dvtm*|xterm*|rxvt*) PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD/$HOME/~}\007"' ;; *) ;; esac Other shells provide similar functionality, zsh as an example has a [precmd function](http://zsh.sourceforge.net/Doc/Release/Functions.html#Hook-Functions) which can be used to achieve the same effect. ### Something is wrong with the displayed colors Make sure you have set `$TERM` correctly for example if you want to use 256 color profiles you probably have to append `-256color` to your regular terminal name. Also due to limitations of ncurses by default you can only use 255 color pairs simultaneously. If you need more than 255 different color pairs at the same time, then you have to rebuild ncurses with $ ./configure ... --enable-ext-colors Note that this changes the ABI and therefore sets SONAME of the library to 6 (i.e. you have to link against `libncursesw.so.6`). ### Some characters are displayed like garbage Make sure you compiled dvtm against a unicode aware curses library (in case of ncurses this would be `libncursesw`). Also make sure that your locale settings contain UTF-8. ### The numeric keypad does not work with Putty Disable [application keypad mode](http://the.earth.li/~sgtatham/putty/0.64/htmldoc/Chapter4.html#config-features-application) in the Putty configuration under `Terminal => Features => Disable application keypad mode`. ### Unicode characters do not work within Putty You have to tell Putty in which [character encoding](http://the.earth.li/~sgtatham/putty/0.64/htmldoc/Chapter4.html#config-translation) the received data is. Set the dropdown box under `Window => Translation` to UTF-8. In order to get proper line drawing characters you proabably also want to set the TERM environment variable to `putty` or `putty-256color`. If that still doesn't do the trick then try running dvtm with the following ncurses related environment variable set `NCURSES_NO_UTF8_ACS=1`. ## Development You can always fetch the current code base from the git repository. git clone https://github.com/martanne/dvtm.git or git clone git://repo.or.cz/dvtm.git If you have comments, suggestions, ideas, a bug report, a patch or something else related to abduco then write to the [suckless developer mailing list](http://suckless.org/community) or contact me directly mat[at]brain-dump.org. [![Build Status](https://travis-ci.org/martanne/dvtm.svg?branch=master)](https://travis-ci.org/martanne/dvtm) [![Coverity Scan Build Status](https://scan.coverity.com/projects/4256/badge.svg)](https://scan.coverity.com/projects/4256) ## License dvtm reuses some code of dwm and is released under the same [MIT/X11 license](https://raw.githubusercontent.com/martanne/dvtm/master/LICENSE). The terminal emulation part is licensed under the ISC license. dvtm-0.15/LICENSE0000644000175000017500000000222312644170710012045 0ustar marcmarcMIT/X Consortium License (c) 2006-2007 Anselm R. Garbe (c) 2007-2016 Marc André 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.15/vstack.c0000644000175000017500000000125412644170710012502 0ustar marcmarc/* A vertical stack layout, all windows have the full screen width. */ static void vstack(void) { unsigned int i, n, ny, nh, m, mh, th; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; m = MAX(1, MIN(n, screen.nmaster)); mh = (n == m ? wah : screen.mfact * wah); th = n == m ? 0 : (wah - mh) / (n - m); ny = way; for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; if (i < m) /* master */ nh = (i < m - 1) ? mh / m : (way + mh) - ny; else /* tile window */ nh = (i < n - 1) ? th : (way + wah) - ny; resize(c, wax, ny, waw, nh); ny += nh; i++; } } dvtm-0.15/fibonacci.c0000644000175000017500000000371112644170710013124 0ustar marcmarcstatic void fibonacci(int s) { unsigned int nx, ny, nw, nnw, nh, nnh, i, n, mod; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; /* 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; /* set the mod factor, 2 for dwindle, 4 for spiral */ mod = s ? 4 : 2; for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; /* 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 client */ if (i < n - 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 client */ if (i < n - 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 client */ if (i < n - 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 client */ if (i < n - 1) { nh /= 2; nnh -= nh; ny += nnh; } mvaddch(ny, nx - 1, ACS_LTEE); } resize(c, nx, ny, nw, nh); i++; } } static void spiral(void) { fibonacci(1); } static void dwindle(void) { fibonacci(0); } dvtm-0.15/forkpty-aix.c0000644000175000017500000000432612644170710013467 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 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); } setsid(); fd = open(_PATH_TTY, O_RDWR|O_NOCTTY); if (fd >= 0) return -1; fd = open(path, O_RDWR); if (fd < 0) return -1; close(fd); fd = open("/dev/tty", O_WRONLY); if (fd < 0) return -1; close(fd); if (tcgetattr(slave, &tio2) != 0) return -1; 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) return -1; if (ioctl(slave, TIOCSWINSZ, ws) == -1) return -1; 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.15/dvtm.c0000644000175000017500000011204512644170710012162 0ustar marcmarc/* * The initial "port" of dwm to curses was done by * * © 2007-2016 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined __CYGWIN__ || defined __sun # include #endif #include "vt.h" #ifdef PDCURSES int ESCDELAY; #endif #ifndef NCURSES_REENTRANT # define set_escdelay(d) (ESCDELAY = (d)) #endif typedef struct { float mfact; unsigned int nmaster; int history; int w; int h; volatile sig_atomic_t need_resize; } Screen; typedef struct { const char *symbol; void (*arrange)(void); } Layout; typedef struct Client Client; struct Client { WINDOW *window; Vt *term; Vt *editor, *app; int editor_fds[2]; volatile sig_atomic_t editor_died; const char *cmd; char title[255]; int order; pid_t pid; 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 urgent; volatile sig_atomic_t died; Client *next; Client *prev; Client *snext; unsigned int tags; }; typedef struct { short fg; short bg; short fg256; short bg256; short pair; } Color; typedef struct { const char *title; attr_t attrs; Color *color; } 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[]); const char *args[MAX_ARGS]; } Action; #define MAX_KEYS 3 typedef unsigned int KeyCombo[MAX_KEYS]; typedef struct { KeyCombo keys; Action action; } KeyBinding; typedef struct { mmask_t mask; Action action; } Button; typedef struct { const char *name; Action action; } Cmd; enum { BAR_TOP, BAR_BOTTOM, BAR_OFF }; typedef struct { int fd; int pos, lastpos; bool autohide; 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; typedef struct { char *data; size_t len; size_t size; } Register; typedef struct { char *name; const char *argv[4]; bool filter; bool color; } Editor; #define LENGTH(arr) (sizeof(arr) / sizeof((arr)[0])) #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define TAGMASK ((1 << LENGTH(tags)) - 1) #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 incnmaster(const char *args[]); static void setmfact(const char *args[]); static void startup(const char *args[]); static void tag(const char *args[]); static void togglebar(const char *args[]); static void togglebarpos(const char *args[]); static void toggleminimize(const char *args[]); static void togglemouse(const char *args[]); static void togglerunall(const char *args[]); static void toggletag(const char *args[]); static void toggleview(const char *args[]); static void viewprevtag(const char *args[]); static void view(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 Client* nextvisible(Client *c); static void focus(Client *c); 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; #include "config.h" /* global variables */ static const char *dvtm_name = "dvtm"; Screen screen = { .mfact = MFACT, .nmaster = NMASTER, .history = SCROLL_HISTORY }; static Client *stack = NULL; static Client *sel = NULL; static Client *lastsel = NULL; static Client *msel = NULL; static unsigned int seltags; static unsigned int tagset[2] = { 1, 1 }; static bool mouse_events_enabled = ENABLE_MOUSE; static Layout *layout = layouts; static StatusBar bar = { .fd = -1, .lastpos = BAR_POS, .pos = BAR_POS, .autohide = BAR_AUTOHIDE, .h = 1 }; static CmdFifo cmdfifo = { .fd = -1 }; static const char *shell; static Register copyreg; 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 isvisible(Client *c) { return c->tags & tagset[seltags]; } static bool is_content_visible(Client *c) { if (!c) return false; if (isarrange(fullscreen)) return sel == c; return isvisible(c) && !c->minimized; } static Client* nextvisible(Client *c) { for (; c && !isvisible(c); c = c->next); return c; } static void updatebarpos(void) { bar.y = 0; wax = 0; way = 0; wah = screen.h; waw = screen.w; 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 hidebar(void) { if (bar.pos != BAR_OFF) { bar.lastpos = bar.pos; bar.pos = BAR_OFF; } } static void showbar(void) { if (bar.pos == BAR_OFF) bar.pos = bar.lastpos; } static void drawbar(void) { int sx, sy, x, y, width; unsigned int occupied = 0, urgent = 0; if (bar.pos == BAR_OFF) return; for (Client *c = clients; c; c = c->next) { occupied |= c->tags; if (c->urgent) urgent |= c->tags; } getyx(stdscr, sy, sx); attrset(BAR_ATTR); move(bar.y, 0); for (unsigned int i = 0; i < LENGTH(tags); i++){ if (tagset[seltags] & (1 << i)) attrset(TAG_SEL); else if (urgent & (1 << i)) attrset(TAG_URGENT); else if (occupied & (1 << i)) attrset(TAG_OCCUPIED); else attrset(TAG_NORMAL); printw(TAG_SYMBOL, tags[i]); } attrset(TAG_NORMAL); addstr(layout->symbol); getyx(stdscr, y, x); (void)y; int maxwidth = screen.w - x - 2; addch(BAR_BEGIN); attrset(BAR_ATTR); wchar_t wbuf[sizeof bar.text]; size_t numchars = mbstowcs(wbuf, bar.text, sizeof bar.text); if (numchars != (size_t)-1 && (width = wcswidth(wbuf, maxwidth)) != -1) { int pos; for (pos = 0; pos + width < maxwidth; pos++) addch(' '); for (size_t i = 0; i < numchars; i++) { pos += wcwidth(wbuf[i]); if (pos > maxwidth) break; addnwstr(wbuf+i, 1); } clrtoeol(); } attrset(TAG_NORMAL); mvaddch(bar.y, screen.w - 1, BAR_END); attrset(NORMAL_ATTR); move(sy, sx); wnoutrefresh(stdscr); } static int show_border(void) { return (bar.pos != BAR_OFF) || (clients && clients->next); } static void draw_border(Client *c) { char t = '\0'; int x, y, maxlen, attrs = NORMAL_ATTR; if (!show_border()) return; if (sel == c || (runinall && !c->minimized)) attrs = SELECTED_ATTR; if (sel != c && c->urgent) attrs = URGENT_ATTR; wattrset(c->window, attrs); getyx(c->window, y, x); mvwhline(c->window, 0, 0, ACS_HLINE, c->w); maxlen = c->w - 10; 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, "[%s%s#%d]", *c->title ? c->title : "", *c->title ? " | " : "", 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(void) { if (!nextvisible(clients)) { sel = NULL; curs_set(0); erase(); drawbar(); doupdate(); return; } if (!isarrange(fullscreen)) { for (Client *c = nextvisible(clients); c; c = nextvisible(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(void) { unsigned int m = 0, n = 0; for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { c->order = ++n; if (c->minimized) m++; } erase(); attrset(NORMAL_ATTR); if (bar.fd == -1 && bar.autohide) { if ((!clients || !clients->next) && n == 1) hidebar(); else showbar(); updatebarpos(); } if (m && !isarrange(fullscreen)) wah--; layout->arrange(); if (m && !isarrange(fullscreen)) { unsigned int i = 0, nw = waw / m, nx = wax; for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) { resize(c, nx, way+wah, ++i == m ? waw - nx : nw, 1); nx += nw; } } wah++; } focus(NULL); wnoutrefresh(stdscr); drawbar(); 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 = nextvisible(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 = nextvisible(c->next)) c->order = ++o; } } static void attachstack(Client *c) { c->snext = stack; stack = c; } 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 = nextvisible(c->next); d; d = nextvisible(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 detachstack(Client *c) { Client **tc; for (tc=&stack; *tc && *tc != c; tc=&(*tc)->snext); *tc = c->snext; } static void focus(Client *c) { if (!c) for (c = stack; c && !isvisible(c); c = c->snext); if (sel == c) return; lastsel = sel; sel = c; if (lastsel) { lastsel->urgent = false; if (!isarrange(fullscreen)) { draw_border(lastsel); wnoutrefresh(lastsel->window); } } if (c) { detachstack(c); attachstack(c); settitle(c); c->urgent = false; if (isarrange(fullscreen)) { draw(c); } else { draw_border(c); wnoutrefresh(c->window); } } curs_set(c && !c->minimized && vt_cursor_visible(c->term)); } static void applycolorrules(Client *c) { const ColorRule *r = colorrules; short fg = r->color->fg, bg = r->color->bg; attr_t attrs = r->attrs; for (unsigned int i = 1; i < LENGTH(colorrules); i++) { r = &colorrules[i]; if (strstr(c->title, r->title)) { attrs = r->attrs; fg = r->color->fg; bg = r->color->bg; break; } } vt_default_colors_set(c->term, attrs, fg, bg); } static void term_title_handler(Vt *term, const char *title) { Client *c = (Client *)vt_data_get(term); if (title) strncpy(c->title, title, sizeof(c->title) - 1); c->title[title ? sizeof(c->title) - 1 : 0] = '\0'; settitle(c); if (!isarrange(fullscreen) || sel == c) draw_border(c); applycolorrules(c); } static void term_urgent_handler(Vt *term) { Client *c = (Client *)vt_data_get(term); c->urgent = true; printf("\a"); fflush(stdout); drawbar(); if (!isarrange(fullscreen) && sel != c && isvisible(c)) draw_border(c); } 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->app, h - has_title_line, w); if (c->editor) vt_resize(c->editor, 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_coord(unsigned int x, unsigned int y) { if (y < way || y >= wah) return NULL; if (isarrange(fullscreen)) return sel; for (Client *c = nextvisible(clients); c; c = nextvisible(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); for (Client *c = clients; c; c = c->next) { if (c->pid == pid) { c->died = true; break; } if (c->editor && vt_pid_get(c->editor) == pid) { c->editor_died = true; break; } } } errno = errsv; } static void sigwinch_handler(int sig) { screen.need_resize = true; } static void sigterm_handler(int sig) { running = false; } static void resize_screen(void) { 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); updatebarpos(); clear(); arrange(); } static KeyBinding* keybinding(KeyCombo keys) { unsigned int keycount = 0; while (keycount < MAX_KEYS && keys[keycount]) keycount++; for (unsigned int b = 0; b < LENGTH(bindings); b++) { for (unsigned int k = 0; k < keycount; k++) { if (keys[k] != bindings[b].keys[k]) break; if (k == keycount - 1) return &bindings[b]; } } return NULL; } static unsigned int bitoftag(const char *tag) { unsigned int i; for (i = 0; (i < LENGTH(tags)) && (tags[i] != tag); i++); return (i < LENGTH(tags)) ? (1 << i) : ~0; } static void tagschanged() { bool allminimized = true; for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { if (!c->minimized) { allminimized = false; break; } } if (allminimized && nextvisible(clients)) { focus(NULL); toggleminimize(NULL); } arrange(); } static void tag(const char *args[]) { if (!sel) return; sel->tags = bitoftag(args[0]) & TAGMASK; tagschanged(); } static void toggletag(const char *args[]) { if (!sel) return; unsigned int newtags = sel->tags ^ (bitoftag(args[0]) & TAGMASK); if (newtags) { sel->tags = newtags; tagschanged(); } } static void toggleview(const char *args[]) { unsigned int newtagset = tagset[seltags] ^ (bitoftag(args[0]) & TAGMASK); if (newtagset) { tagset[seltags] = newtagset; tagschanged(); } } static void view(const char *args[]) { unsigned int newtagset = bitoftag(args[0]) & TAGMASK; if (tagset[seltags] != newtagset && newtagset) { seltags ^= 1; /* toggle sel tagset */ tagset[seltags] = newtagset; tagschanged(); } } static void viewprevtag(const char *args[]) { seltags ^= 1; tagschanged(); } 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 ? nextvisible(clients) : sel; c; c = nextvisible(c->next)) { if (is_content_visible(c)) { c->urgent = false; if (code == '\e') vt_write(c->term, buf, len); else vt_keypress(c->term, code); } if (!runinall) break; } } static void mouse_setup(void) { #ifdef CONFIG_MOUSE mmask_t mask = 0; if (mouse_events_enabled) { mask = BUTTON1_CLICKED | BUTTON2_CLICKED; for (unsigned int i = 0; i < LENGTH(buttons); i++) mask |= buttons[i].mask; } mousemask(mask, NULL); #endif /* CONFIG_MOUSE */ } static bool checkshell(const char *shell) { if (shell == NULL || *shell == '\0' || *shell != '/') return false; if (!strcmp(strrchr(shell, '/')+1, dvtm_name)) return false; if (access(shell, X_OK)) return false; return true; } static const char * getshell(void) { const char *shell = getenv("SHELL"); struct passwd *pw; if (checkshell(shell)) return shell; if ((pw = getpwuid(getuid())) && checkshell(pw->pw_shell)) return pw->pw_shell; return "/bin/sh"; } static void setup(void) { shell = getshell(); setlocale(LC_CTYPE, ""); initscr(); start_color(); noecho(); nonl(); keypad(stdscr, TRUE); mouse_setup(); raw(); vt_init(); vt_keytable_set(keytable, LENGTH(keytable)); for (unsigned int i = 0; i < LENGTH(colors); i++) { if (COLORS == 256) { if (colors[i].fg256) colors[i].fg = colors[i].fg256; if (colors[i].bg256) colors[i].bg = colors[i].bg256; } colors[i].pair = vt_color_reserve(colors[i].fg, colors[i].bg); } 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); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); } static void destroy(Client *c) { if (sel == c) focusnextnm(NULL); detach(c); detachstack(c); if (sel == c) { Client *next = nextvisible(clients); if (next) { focus(next); 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 && LENGTH(actions)) { if (!strcmp(c->cmd, shell)) quit(NULL); else create(NULL); } free(c); arrange(); } static void cleanup(void) { while (clients) destroy(clients); vt_shutdown(); endwin(); free(copyreg.data); 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); } static void create(const char *args[]) { const char *pargs[4] = { shell, NULL }; char buf[8], *cwd = NULL; const char *env[] = { "DVTM_WINDOW_ID", buf, NULL }; if (args && args[0]) { pargs[1] = "-c"; pargs[2] = args[0]; pargs[3] = NULL; } Client *c = calloc(1, sizeof(Client)); if (!c) return; c->tags = tagset[seltags]; c->id = ++cmdfifo.id; snprintf(buf, sizeof buf, "%d", c->id); if (!(c->window = newwin(wah, waw, way, wax))) { free(c); return; } c->term = c->app = vt_create(screen.h, screen.w, screen.history); if (!c->term) { delwin(c->window); free(c); return; } c->cmd = (args && args[0]) ? args[0] : shell; 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, shell, pargs, cwd, env, NULL, NULL); if (args && args[2] && !strcmp(args[2], "$CWD")) free(cwd); vt_data_set(c->term, c); vt_title_handler_set(c->term, term_title_handler); vt_urgent_handler_set(c->term, term_urgent_handler); c->x = wax; c->y = way; debug("client with pid %d forked\n", c->pid); attach(c); focus(c); arrange(); } static void copymode(const char *args[]) { if (!sel || sel->editor) return; if (!(sel->editor = vt_create(sel->h - sel->has_title_line, sel->w, 0))) return; char *ed = getenv("DVTM_EDITOR"); int *to = &sel->editor_fds[0], *from = NULL; sel->editor_fds[0] = sel->editor_fds[1] = -1; if (!ed) ed = getenv("EDITOR"); if (!ed) ed = getenv("PAGER"); if (!ed) ed = editors[0].name; const char **argv = (const char*[]){ ed, "-", NULL, NULL }; char argline[32]; bool colored = false; for (unsigned int i = 0; i < LENGTH(editors); i++) { if (!strcmp(editors[i].name, ed)) { for (int j = 1; editors[i].argv[j]; j++) { if (strstr(editors[i].argv[j], "%d")) { int line = vt_content_start(sel->app); snprintf(argline, sizeof(argline), "+%d", line); argv[j] = argline; } else { argv[j] = editors[i].argv[j]; } } if (editors[i].filter) from = &sel->editor_fds[1]; colored = editors[i].color; break; } } if (vt_forkpty(sel->editor, ed, argv, NULL, NULL, to, from) < 0) { vt_destroy(sel->editor); sel->editor = NULL; return; } sel->term = sel->editor; if (sel->editor_fds[0] != -1) { char *buf = NULL; size_t len = vt_content_get(sel->app, &buf, colored); char *cur = buf; while (len > 0) { ssize_t res = write(sel->editor_fds[0], cur, len); if (res < 0) { if (errno == EAGAIN || errno == EINTR) continue; break; } cur += res; len -= res; } free(buf); close(sel->editor_fds[0]); sel->editor_fds[0] = -1; } if (args[0]) vt_write(sel->editor, args[0], strlen(args[0])); } static void focusn(const char *args[]) { for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->order == atoi(args[0])) { focus(c); if (c->minimized) toggleminimize(NULL); return; } } } static void focusnext(const char *args[]) { Client *c; if (!sel) return; for (c = sel->next; c && !isvisible(c); c = c->next); if (!c) for (c = clients; c && !isvisible(c); c = c->next); if (c) focus(c); } static void focusnextnm(const char *args[]) { if (!sel) return; Client *c = sel; do { c = nextvisible(c->next); if (!c) c = nextvisible(clients); } while (c->minimized && c != sel); focus(c); } static void focusprev(const char *args[]) { Client *c; if (!sel) return; for (c = sel->prev; c && !isvisible(c); c = c->prev); if (!c) { for (c = clients; c && c->next; c = c->next); for (; c && !isvisible(c); c = c->prev); } if (c) focus(c); } static void focusprevnm(const char *args[]) { if (!sel) return; Client *c = sel; do { for (c = c->prev; c && !isvisible(c); c = c->prev); if (!c) { for (c = clients; c && c->next; c = c->next); for (; c && !isvisible(c); c = c->prev); } } while (c && c != sel && c->minimized); 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 && copyreg.data) vt_write(sel->term, copyreg.data, copyreg.len); } 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_visible(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[LENGTH(layouts)]) layout = &layouts[0]; } else { for (i = 0; i < LENGTH(layouts); i++) if (!strcmp(args[0], layouts[i].symbol)) break; if (i == LENGTH(layouts)) return; layout = &layouts[i]; } arrange(); } static void incnmaster(const char *args[]) { int delta; if (isarrange(fullscreen) || isarrange(grid)) return; /* arg handling, manipulate nmaster */ if (args[0] == NULL) { screen.nmaster = NMASTER; } else if (sscanf(args[0], "%d", &delta) == 1) { if (args[0][0] == '+' || args[0][0] == '-') screen.nmaster += delta; else screen.nmaster = delta; if (screen.nmaster < 1) screen.nmaster = 1; } 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 (sscanf(args[0], "%f", &delta) == 1) { 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 < LENGTH(actions); i++) actions[i].cmd(actions[i].args); } static void togglebar(const char *args[]) { if (bar.pos == BAR_OFF) showbar(); else hidebar(); bar.autohide = false; updatebarpos(); redraw(NULL); } static void togglebarpos(const char *args[]) { switch (bar.pos == BAR_OFF ? bar.lastpos : bar.pos) { case BAR_TOP: bar.pos = BAR_BOTTOM; break; case BAR_BOTTOM: bar.pos = BAR_TOP; break; } updatebarpos(); redraw(NULL); } static void toggleminimize(const char *args[]) { Client *c, *m, *t; unsigned int n; if (!sel) return; /* the last window can't be minimized */ if (!sel->minimized) { for (n = 0, c = nextvisible(clients); c; c = nextvisible(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 == nextvisible(clients) && sel->minimized) { c = nextvisible(sel->next); detach(c); attach(c); focus(c); detach(m); for (; c && (t = nextvisible(c->next)) && !t->minimized; c = t); 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 = nextvisible(clients); c && (t = nextvisible(c->next)) && !t->minimized; c = t); 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) == nextvisible(clients)) if (!(c = nextvisible(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 < LENGTH(commands); i++) { if (!strcmp(name, commands[i].name)) return &commands[i]; } return NULL; } static void handle_cmdfifo(void) { 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; const char *args[MAX_ARGS], *arg; memset(args, 0, sizeof(args)); /* 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(void) { #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 < LENGTH(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(void) { 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 void handle_editor(Client *c) { if (!copyreg.data && (copyreg.data = malloc(screen.history))) copyreg.size = screen.history; copyreg.len = 0; while (c->editor_fds[1] != -1 && copyreg.len < copyreg.size) { ssize_t len = read(c->editor_fds[1], copyreg.data + copyreg.len, copyreg.size - copyreg.len); if (len == -1) { if (errno == EINTR) continue; break; } if (len == 0) break; copyreg.len += len; if (copyreg.len == copyreg.size) { copyreg.size *= 2; if (!(copyreg.data = realloc(copyreg.data, copyreg.size))) { copyreg.size = 0; copyreg.len = 0; } } } c->editor_died = false; c->editor_fds[1] = -1; vt_destroy(c->editor); c->editor = NULL; c->term = c->app; vt_dirty(c->term); draw_content(c); wnoutrefresh(c->window); } 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(void) { 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; const char *name = argv[0]; if (name && (name = strrchr(name, '/'))) dvtm_name = name + 1; 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-2016 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 b = 0; b < LENGTH(bindings); b++) if (bindings[b].keys[0] == MOD) bindings[b].keys[0] = *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[]) { KeyCombo keys; unsigned int key_index = 0; memset(keys, 0, sizeof(keys)); sigset_t emptyset, blockset; setenv("DVTM", VERSION, 1); if (!parse_args(argc, argv)) { setup(); startup(NULL); } sigemptyset(&emptyset); sigemptyset(&blockset); sigaddset(&blockset, SIGWINCH); sigaddset(&blockset, SIGCHLD); sigprocmask(SIG_BLOCK, &blockset, 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->editor && c->editor_died) handle_editor(c); if (!c->editor && c->died) { Client *t = c->next; destroy(c); c = t; continue; } int pty = c->editor ? vt_pty_get(c->editor) : vt_pty_get(c->app); FD_SET(pty, &rd); nfds = MAX(nfds, pty); c = c->next; } doupdate(); r = pselect(nfds + 1, &rd, NULL, NULL, NULL, &emptyset); if (r == -1 && errno == EINTR) continue; if (r < 0) { perror("select()"); exit(EXIT_FAILURE); } if (FD_ISSET(STDIN_FILENO, &rd)) { int code = getch(); if (code >= 0) { keys[key_index++] = code; KeyBinding *binding = NULL; if (code == KEY_MOUSE) { key_index = 0; handle_mouse(); } else if ((binding = keybinding(keys))) { unsigned int key_length = 0; while (key_length < MAX_KEYS && binding->keys[key_length]) key_length++; if (key_index == key_length) { binding->action.cmd(binding->action.args); key_index = 0; memset(keys, 0, sizeof(keys)); } } else { key_index = 0; memset(keys, 0, sizeof(keys)); 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; c = c->next) { if (FD_ISSET(vt_pty_get(c->term), &rd)) { if (vt_process(c->term) < 0 && errno == EIO) { if (c->editor) c->editor_died = true; else c->died = true; continue; } } if (c != sel && is_content_visible(c)) { draw_content(c); wnoutrefresh(c->window); } } if (is_content_visible(sel)) { draw_content(sel); curs_set(vt_cursor_visible(sel->term)); wnoutrefresh(sel->window); } } cleanup(); return 0; } dvtm-0.15/fullscreen.c0000644000175000017500000000020712644170710013346 0ustar marcmarcstatic void fullscreen(void) { for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) resize(c, wax, way, waw, wah); } dvtm-0.15/tstack.c0000644000175000017500000000166412644170710012505 0ustar marcmarcstatic void tstack(void) { unsigned int i, n, nx, ny, nw, nh, m, mw, mh, tw; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; m = MAX(1, MIN(n, screen.nmaster)); mh = n == m ? wah : screen.mfact * wah; mw = waw / m; tw = n == m ? 0 : waw / (n - m); nx = wax; ny = way + wah - mh; for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; if (i < m) { /* master */ if (i > 0) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++; } nh = mh; nw = (i < m - 1) ? mw : (wax + waw) - nx; } else { /* tile window */ if (i == m) { nx = wax; ny = way; nh = (way + wah) - ny - mh; } if (i > m) { mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++; } nw = (i < n - 1) ? tw : (wax + waw) - nx; } resize(c, nx, ny, nw, nh); nx += nw; i++; } } dvtm-0.15/config.def.h0000644000175000017500000002472012644170710013221 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 */ enum { DEFAULT, BLUE, }; static Color colors[] = { [DEFAULT] = { .fg = -1, .bg = -1, .fg256 = -1, .bg256 = -1, }, [BLUE] = { .fg = COLOR_BLUE, .bg = -1, .fg256 = 68, .bg256 = -1, }, }; #define COLOR(c) COLOR_PAIR(colors[c].pair) /* curses attributes for the currently focused window */ #define SELECTED_ATTR (COLOR(BLUE) | A_NORMAL) /* curses attributes for normal (not selected) windows */ #define NORMAL_ATTR (COLOR(DEFAULT) | A_NORMAL) /* curses attributes for a window with pending urgent flag */ #define URGENT_ATTR NORMAL_ATTR /* curses attributes for the status bar */ #define BAR_ATTR (COLOR(BLUE) | A_NORMAL) /* characters for beginning and end of status bar message */ #define BAR_BEGIN '[' #define BAR_END ']' /* status bar (command line option -s) position */ #define BAR_POS BAR_TOP /* BAR_BOTTOM, BAR_OFF */ /* whether status bar should be hidden if only one client exists */ #define BAR_AUTOHIDE true /* master width factor [0.1 .. 0.9] */ #define MFACT 0.5 /* number of clients in master area */ #define NMASTER 1 /* scroll back buffer size in lines */ #define SCROLL_HISTORY 500 /* printf format string for the tag in the status bar */ #define TAG_SYMBOL "[%s]" /* curses attributes for the currently selected tags */ #define TAG_SEL (COLOR(BLUE) | A_BOLD) /* curses attributes for not selected tags which contain no windows */ #define TAG_NORMAL (COLOR(DEFAULT) | A_NORMAL) /* curses attributes for not selected tags which contain windows */ #define TAG_OCCUPIED (COLOR(BLUE) | A_NORMAL) /* curses attributes for not selected tags which with urgent windows */ #define TAG_URGENT (COLOR(BLUE) | A_NORMAL | A_BLINK) const char tags[][8] = { "1", "2", "3", "4", "5" }; #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') #define TAGKEYS(KEY,TAG) \ { { MOD, 'v', KEY, }, { view, { tags[TAG] } } }, \ { { MOD, 't', KEY, }, { tag, { tags[TAG] } } }, \ { { MOD, 'V', KEY, }, { toggleview, { tags[TAG] } } }, \ { { MOD, 'T', KEY, }, { toggletag, { tags[TAG] } } }, /* you can at most specifiy MAX_ARGS (3) number of arguments */ static KeyBinding bindings[] = { { { MOD, 'c', }, { create, { NULL } } }, { { MOD, 'C', }, { create, { NULL, NULL, "$CWD" } } }, { { MOD, 'x', 'x', }, { killclient, { NULL } } }, { { MOD, 'j', }, { focusnext, { NULL } } }, { { MOD, 'J', }, { focusnextnm, { NULL } } }, { { MOD, 'K', }, { focusprevnm, { NULL } } }, { { MOD, 'k', }, { focusprev, { NULL } } }, { { MOD, 'f', }, { setlayout, { "[]=" } } }, { { MOD, 'g', }, { setlayout, { "+++" } } }, { { MOD, 'b', }, { setlayout, { "TTT" } } }, { { MOD, 'm', }, { setlayout, { "[ ]" } } }, { { MOD, ' ', }, { setlayout, { NULL } } }, { { MOD, 'i', }, { incnmaster, { "+1" } } }, { { MOD, 'd', }, { incnmaster, { "-1" } } }, { { MOD, 'h', }, { setmfact, { "-0.05" } } }, { { MOD, 'l', }, { setmfact, { "+0.05" } } }, { { MOD, '.', }, { toggleminimize, { NULL } } }, { { MOD, 's', }, { togglebar, { NULL } } }, { { MOD, 'S', }, { togglebarpos, { NULL } } }, { { MOD, 'M', }, { togglemouse, { NULL } } }, { { MOD, '\n', }, { zoom , { NULL } } }, { { MOD, '\r', }, { 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', 'q', }, { quit, { NULL } } }, { { MOD, 'a', }, { togglerunall, { NULL } } }, { { MOD, CTRL('L'), }, { redraw, { NULL } } }, { { MOD, 'r', }, { redraw, { NULL } } }, { { MOD, 'e', }, { copymode, { NULL } } }, { { MOD, '/', }, { copymode, { "/" } } }, { { MOD, 'p', }, { paste, { NULL } } }, { { MOD, KEY_PPAGE, }, { scrollback, { "-1" } } }, { { MOD, KEY_NPAGE, }, { scrollback, { "1" } } }, { { MOD, '?', }, { create, { "man dvtm", "dvtm help" } } }, { { MOD, MOD, }, { send, { (const char []){MOD, 0} } } }, { { KEY_SPREVIOUS, }, { scrollback, { "-1" } } }, { { KEY_SNEXT, }, { scrollback, { "1" } } }, { { MOD, '0', }, { view, { NULL } } }, { { MOD, KEY_F(1), }, { view, { tags[0] } } }, { { MOD, KEY_F(2), }, { view, { tags[1] } } }, { { MOD, KEY_F(3), }, { view, { tags[2] } } }, { { MOD, KEY_F(4), }, { view, { tags[3] } } }, { { MOD, KEY_F(5), }, { view, { tags[4] } } }, { { MOD, 'v', '0' }, { view, { NULL } } }, { { MOD, 'v', '\t', }, { viewprevtag, { NULL } } }, { { MOD, 't', '0' }, { tag, { NULL } } }, TAGKEYS( '1', 0) TAGKEYS( '2', 1) TAGKEYS( '3', 2) TAGKEYS( '4', 3) TAGKEYS( '5', 4) }; static const ColorRule colorrules[] = { { "", A_NORMAL, &colors[DEFAULT] }, /* default */ }; /* 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 */ }; /* editor to use for copy mode. If neither of DVTM_EDITOR, EDITOR and PAGER is * set the first entry is chosen. Otherwise the array is consulted for supported * options. A %d in argv is replaced by the line number at which the file should * be opened. If filter is true the editor is expected to work even if stdout is * redirected (i.e. not a terminal). If color is true then color escape sequences * are generated in the output. */ static Editor editors[] = { { .name = "vis", .argv = { "vis", "+%d", "-", NULL }, .filter = true, .color = false }, { .name = "sandy", .argv = { "sandy", "-d", "-", NULL }, .filter = true, .color = false }, { .name = "dvtm-editor", .argv = { "dvtm-editor", "-", NULL }, .filter = true, .color = false }, { .name = "vim", .argv = { "vim", "+%d", "-", NULL }, .filter = false, .color = false }, { .name = "less", .argv = { "less", "-R", "+%d", NULL }, .filter = false, .color = true }, { .name = "more", .argv = { "more", "+%d", NULL }, .filter = false, .color = false }, }; dvtm-0.15/Makefile0000644000175000017500000000345212644170710012505 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.md testsuite.sh config.def.h config.mk \ ${SRC} vt.h forkpty-aix.c forkpty-sunos.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} 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 @TERMINFO=${TERMINFO} tic -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.15/tile.c0000644000175000017500000000201412644170710012137 0ustar marcmarcstatic void tile(void) { unsigned int i, n, nx, ny, nw, nh, m, mw, mh, th; Client *c; for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) if (!c->minimized) n++; m = MAX(1, MIN(n, screen.nmaster)); mw = n == m ? waw : screen.mfact * waw; mh = wah / m; th = n == m ? 0 : wah / (n - m); nx = wax; ny = way; for (i = 0, c = nextvisible(clients); c; c = nextvisible(c->next)) { if (c->minimized) continue; if (i < m) { /* master */ nw = mw; nh = (i < m - 1) ? mh : (way + wah) - ny; } else { /* tile window */ if (i == m) { ny = way; nx += mw; mvvline(ny, nx, ACS_VLINE, wah); mvaddch(ny, nx, ACS_TTEE); nx++; nw = waw - mw -1; } nh = (i < n - 1) ? th : (way + wah) - ny; if (i > m) mvaddch(ny, nx - 1, ACS_LTEE); } resize(c, nx, ny, nw, nh); ny += nh; i++; } /* Fill in nmaster intersections */ if (n > m) { ny = way + mh; for (i = 1; i < m; i++) { mvaddch(ny, nx - 1, ((ny - 1) % th ? ACS_RTEE : ACS_PLUS)); ny += mh; } } } dvtm-0.15/dvtm.10000644000175000017500000001373312644170710012104 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\-C Create a new shell window using the current working directory of the focused window. .TP .B Mod\-x\-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\-i Increase number of windows displayed in the master area. .TP .B Mod\-d Decrease number of windows displayed in the master area. .TP .B Mod\-j Focus next window. .TP .B Mod\-k Focus previous window. .TP .B Mod\-J Focus next non minimized window. .TP .B Mod\-K Focus previous non minimized 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\-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\-f 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\-S Toggle position of the status bar between top and bottom. .TP .B Mod\-r .TQ .B Mod\-^L Redraw whole screen. .TP .B Mod\-a Toggle keyboard multiplexing mode, if activated keypresses are sent to all visible windows. .TP .B Mod\-M Toggle dvtm mouse grabbing. .TP .B Mod\-e Enter copy mode (see section below for further information). .TP .B Mod\-/ Enter copy mode and start searching forward (assumes a vi-like editor). .TP .B Mod\-p Paste last copied text from copy mode at current cursor position. .TP .B Mod\-? Show this manual page. .TP .B Mod\-Mod Send the Mod key. .TP .B Mod-F[1..n] .TQ .B Mod-v-[1..n] View all windows with nth tag. .TP .B Mod-0 View all windows with any tag. .TP .B Mod-v-Tab Toggles to the previously selected tags. .TP .B Mod-V-[1..n] Add/remove all windows with nth tag to/from the view. .TP .B Mod-t-[1..n] Apply nth tag to focused window. .TP .B Mod-T-[1..n] Add/remove nth tag to/from focused window. .TP .B Mod\-q\-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 by piping it to an editor. What ever the editor prints to stdout upon exiting will be stored in an internal register and can be pasted into other clients (via .B Mod\-p ) .SH ENVIRONMENT VARIABLES .TP .B DVTM Each process spawned by dvtm will have this variable set to the dvtm version it is running under. .TP .B DVTM_WINDOW_ID Each process also has access to its constant and unique window id. .TP .B DVTM_CMD_FIFO If the -c command line argument was specified upon dvtm startup, this variable will be set to the file name of the named pipe. Thus allowing the process to send commands back to dvtm. .TP .B DVTM_TERM 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. .TP .B DVTM_EDITOR When entering the copymode dvtm pipes the whole scroll back buffer to .BR DVTM_EDITOR which is launched with .BR \- (indicating to read from stdin) as its only argument. If .BR DVTM_EDITOR is not set .BR EDITOR is checked, if this is also not set the default value specified in .BR config.h is used instead. .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