dvtm-0.6/0000755000175000017500000000000011453640555010767 5ustar marcmarcdvtm-0.6/dvtm-status0000755000175000017500000000032711453640555013212 0ustar marcmarc#!/bin/sh FIFO="/tmp/dvtm-status.$$" [ -e "$FIFO" ] || mkfifo "$FIFO" chmod 600 $FIFO while true; do date +%H:%M sleep 60 done > $FIFO & STATUS_PID=$! dvtm -s $FIFO "$@" 2> /dev/null kill $STATUS_PID rm $FIFO dvtm-0.6/config.mk0000644000175000017500000000076711453640555012577 0ustar marcmarc# dvtm version VERSION = 0.6 # Customize below to fit your system PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man INCS = -I. -I/usr/include -I/usr/local/include LIBS = -lc -lutil -lncursesw #LIBS = -lc -lutil -lncurses CFLAGS += -std=c99 -Os ${INCS} -DVERSION=\"${VERSION}\" -DNDEBUG LDFLAGS += -L/usr/lib -L/usr/local/lib ${LIBS} # Mouse handling CFLAGS += -DCONFIG_MOUSE #CFLAGS += -DCONFIG_CMDFIFO CFLAGS += -DCONFIG_STATUSBAR DEBUG_CFLAGS = ${CFLAGS} -UNDEBUG -O0 -g -ggdb -Wall CC = cc dvtm-0.6/grid.c0000644000175000017500000000352611453640555012066 0ustar marcmarcstatic void grid(void) { unsigned int i, m, nm, n, nx, ny, nw, nh, aw, ah, cols, rows; Client *c; for(n = 0, m = 0, c = clients; c; c = c->next,n++) if(c->minimized) m++; /* number of non minimized windows */ nm = n - m; /* grid dimensions */ for(cols = 0; cols <= nm/2; cols++) if(cols*cols >= nm) break; rows = (cols && (cols - 1) * cols >= nm) ? cols - 1 : cols; /* window geoms (cell height/width) */ nh = (wah - m) / (rows ? rows : 1); nw = waw / (cols ? cols : 1); for(i = 0, c = clients; c; c = c->next,i++) { if(!c->minimized){ /* if there are less clients in the last row than normal adjust the * split rate to fill the empty space */ if(rows > 1 && i == (rows * cols) - cols && (nm - i) <= (nm % cols)) nw = waw / (nm - i); nx = (i % cols) * nw + wax; ny = (i / cols) * nh + way; /* adjust height/width of last row/column's windows */ ah = (i >= cols * (rows -1)) ? wah - m - nh * rows : 0; /* special case if there are less clients in the last row */ if(rows > 1 && i == nm - 1 && (nm - i) < (nm % cols)) /* (n % cols) == number of clients in the last row */ aw = waw - nw * (nm % cols); else aw = ((i + 1) % cols == 0) ? waw - nw * cols : 0; if(i % cols){ mvvline(ny, nx, ACS_VLINE, nh + ah); /* if we are on the first row, or on the last one and there are fewer clients * than normal whose border does not match the line above, print a top tree char * otherwise a plus sign. */ if(i <= cols || (i >= rows * cols - cols && nm % cols && (cols - (nm % cols)) % 2)) mvaddch(ny, nx, ACS_TTEE); else mvaddch(ny, nx, ACS_PLUS); nx++, aw--; } } else { if(i == nm){ /* first minimized client */ ny = way + wah - m; nx = wax; nw = waw; nh = 1; aw = 0; ah = 0; } else ny++; } resize(c, nx, ny, nw + aw, nh + ah); } } dvtm-0.6/bstack.c0000644000175000017500000000215111453640555012401 0ustar marcmarcstatic void bstack(void) { unsigned int i, m, n, nx, ny, nw, nh, mh, tw; Client *c; for(n = 0, m = 0, c = clients; c; c = c->next, n++) if(c->minimized) m++; if(n == 1) mh = wah; else if(n - 1 == m) mh = wah - m; else mh = mwfact * (wah - m); /* true if there are at least 2 non minimized clients */ if(n - 1 > m) tw = waw / (n - m - 1); nx = wax; ny = way; for(i = 0, c = clients; c; c = c->next, i++){ if(i == 0){ /* master */ nh = mh; nw = waw; } else { /* tile window */ if(i == 1){ nx = wax; ny += mh; nh = (way + wah - m) - ny; } if(i == n - m - 1){ /* last not minimized client */ nw = (wax + waw) - nx; } else if(i == n - m){ /* first minimized client */ ny += nh; nx = wax; nw = waw; nh = 1; } else if(c->minimized) { /* minimized window */ nw = waw; nh = 1; ny++; } else /* normal non minimized tile window */ nw = tw; if(i > 1 && !c->minimized){ mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); nx++, nw--; } } resize(c,nx,ny,nw,nh); if(n > 1 && i < n - m - 1) nx += nw; } } dvtm-0.6/config.h0000644000175000017500000001423011453640555012405 0ustar marcmarc/* curses attributes for the currently focused window */ /* valid curses attributes are listed below they can be ORed * * A_NORMAL Normal display (no highlight) * A_STANDOUT Best highlighting mode of the terminal. * A_UNDERLINE Underlining * A_REVERSE Reverse video * A_BLINK Blinking * A_DIM Half bright * A_BOLD Extra bright or bold * A_PROTECT Protected mode * A_INVIS Invisible or blank mode * */ #define BLUE (COLORS==256 ? 68 : COLOR_BLUE) #define SELECTED_ATTR A_NORMAL #define SELECTED_FG BLUE #define SELECTED_BG -1 /* curses attributes for normal (not selected) windows */ #define NORMAL_ATTR A_NORMAL #define NORMAL_FG -1 #define NORMAL_BG -1 /* status bar (command line option -s) position */ #define BARPOS BarTop /* BarBot, BarOff */ /* curses attributes for the status bar */ #define BAR_ATTR A_NORMAL #define BAR_FG BLUE #define BAR_BG -1 /* determines whether the statusbar text should be right or left aligned */ #define BAR_ALIGN ALIGN_RIGHT /* separator between window title and window number */ #define SEPARATOR " | " /* printf format string for the window title, first %s * is replaced by the title, second %s is replaced by * the SEPARATOR, %d stands for the window number */ #define TITLE "[%s%s#%d]" /* master width factor [0.1 .. 0.9] */ #define MWFACT 0.5 /* scroll back buffer size in lines */ #define SCROLL_BUF_SIZE 500 #include "tile.c" #include "grid.c" #include "bstack.c" #include "fullscreen.c" /* by default the first layout entry is used */ Layout layouts[] = { { "[]=", tile }, { "+++", grid }, { "TTT", bstack }, { "[ ]", fullscreen }, }; #define MOD CTRL('g') /* you can at most specifiy MAX_ARGS (2) number of arguments */ Key keys[] = { { MOD, 'c', { create, { NULL } } }, { MOD, 'x', { killclient, { NULL } } }, { MOD, 'j', { focusnext, { NULL } } }, { MOD, 'u', { focusnextnm, { NULL } } }, { MOD, 'i', { focusprevnm, { NULL } } }, { MOD, 'k', { focusprev, { NULL } } }, { MOD, 't', { setlayout, { "[]=" } } }, { MOD, 'g', { setlayout, { "+++" } } }, { MOD, 'b', { setlayout, { "TTT" } } }, { MOD, 'm', { setlayout, { "[ ]" } } }, { MOD, ' ', { setlayout, { NULL } } }, { MOD, 'h', { setmwfact, { "-0.05" } } }, { MOD, 'l', { setmwfact, { "+0.05" } } }, { MOD, '.', { toggleminimize, { NULL } } }, #ifdef CONFIG_STATUSBAR { MOD, 's', { togglebar, { NULL } } }, #endif #ifdef CONFIG_MOUSE { MOD, 'M', { mouse_toggle, { NULL } } }, #endif { MOD, '\n', { zoom , { NULL } } }, { MOD, '1', { focusn, { "1" } } }, { MOD, '2', { focusn, { "2" } } }, { MOD, '3', { focusn, { "3" } } }, { MOD, '4', { focusn, { "4" } } }, { MOD, '5', { focusn, { "5" } } }, { MOD, '6', { focusn, { "6" } } }, { MOD, '7', { focusn, { "7" } } }, { MOD, '8', { focusn, { "8" } } }, { MOD, '9', { focusn, { "9" } } }, { MOD, 'q', { quit, { NULL } } }, { MOD, 'G', { escapekey, { NULL } } }, { MOD, 'a', { togglerunall, { NULL } } }, { MOD, 'r', { redraw, { NULL } } }, { MOD, 'X', { lock, { NULL } } }, { MOD, 'B', { togglebell, { NULL } } }, { MOD, KEY_PPAGE, { scrollback, { "-1" } } }, { MOD, KEY_NPAGE, { scrollback, { "1" } } }, { MOD, '?', { create, { "man dvtm", "dvtm help" } } }, }; /* 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 CONFIG_MOUSE 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 */ #ifdef CONFIG_CMDFIFO Cmd commands[] = { { "create", { create, { NULL } } }, }; #endif /* CONFIG_CMDFIFO */ /* gets executed when dvtm is started */ Action actions[] = { { create, { NULL } }, }; dvtm-0.6/LICENSE0000644000175000017500000000223311453640555011774 0ustar marcmarcMIT/X Consortium License (c) 2006-2007 Anselm R. Garbe (c) 2007-2009 Marc Andre Tanner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dvtm-0.6/mouse.c0000644000175000017500000000260611453640555012267 0ustar marcmarcstatic Client *msel = NULL; 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 Client* get_client_by_coord(int x, int y) { Client *c; if (y < way || y >= wah) return NULL; if (isarrange(fullscreen)) return sel; for (c = clients; c; c = c->next) { if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) { debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order); return c; } } return NULL; } static void handle_mouse() { MEVENT event; unsigned int i; if (getmouse(&event) != OK) return; msel = get_client_by_coord(event.x, event.y); if (!msel) return; for (i = 0; i < countof(buttons); i++) if (event.bstate & buttons[i].mask) buttons[i].action.cmd(buttons[i].action.args); msel = NULL; } static void mouse_setup() { int i; mmask_t mask; for (i = 0, mask = 0; i < countof(buttons); i++) mask |= buttons[i].mask; if (mask) mousemask(mask, NULL); } static void mouse_toggle() { static int state = 0; if(!state) mousemask(0, NULL); else mouse_setup(); state = !state; } dvtm-0.6/madtty.c0000644000175000017500000011412111453640555012435 0ustar marcmarc/* LICENSE INFORMATION: This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation. Please refer to the COPYING file for more information. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Copyright © 2004 Bruno T. C. de Oliveira Copyright © 2006 Pierre Habouzit Copyright © 2008 Marc Andre Tanner */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ # include #elif defined(__FreeBSD__) # include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include #endif #ifdef __CYGWIN__ # include #endif #include "madtty.h" #ifndef NCURSES_ATTR_SHIFT # define NCURSES_ATTR_SHIFT 8 #endif #define IS_CONTROL(ch) !((ch) & 0xffffff60UL) static int has_default, is_utf8, use_palette; static const unsigned palette_start = 1; static const unsigned palette_end = 256; static unsigned palette_cur; static short *color2palette; static unsigned color_hash(short f, short b) { return ((f+1) * (COLORS+1)) + b+1 ; } enum { C0_NUL = 0x00, C0_SOH, C0_STX, C0_ETX, C0_EOT, C0_ENQ, C0_ACK, C0_BEL, C0_BS , C0_HT , C0_LF , C0_VT , C0_FF , C0_CR , C0_SO , C0_SI , C0_DLE, C0_DC1, C0_DC2, D0_DC3, C0_DC4, C0_NAK, C0_SYN, C0_ETB, C0_CAN, C0_EM , C0_SUB, C0_ESC, C0_IS4, C0_IS3, C0_IS2, C0_IS1, }; enum { C1_40 = 0x40, C1_41 , C1_BPH, C1_NBH, C1_44 , C1_NEL, C1_SSA, C1_ESA, C1_HTS, C1_HTJ, C1_VTS, C1_PLD, C1_PLU, C1_RI , C1_SS2, C1_SS3, C1_DCS, C1_PU1, C1_PU2, C1_STS, C1_CCH, C1_MW , C1_SPA, C1_EPA, C1_SOS, C1_59 , C1_SCI, C1_CSI, CS_ST , C1_OSC, C1_PM , C1_APC, }; enum { CSI_ICH = 0x40, CSI_CUU, CSI_CUD, CSI_CUF, CSI_CUB, CSI_CNL, CSI_CPL, CSI_CHA, CSI_CUP, CSI_CHT, CSI_ED , CSI_EL , CSI_IL , CSI_DL , CSI_EF , CSI_EA , CSI_DCH, CSI_SEE, CSI_CPR, CSI_SU , CSI_SD , CSI_NP , CSI_PP , CSI_CTC, CSI_ECH, CSI_CVT, CSI_CBT, CSI_SRS, CSI_PTX, CSI_SDS, CSI_SIMD, CSI_5F, CSI_HPA, CSI_HPR, CSI_REP, CSI_DA , CSI_VPA, CSI_VPR, CSI_HVP, CSI_TBC, CSI_SM , CSI_MC , CSI_HPB, CSI_VPB, CSI_RM , CSI_SGR, CSI_DSR, CSI_DAQ, CSI_70 , CSI_71 , CSI_72 , CSI_73 , CSI_74 , CSI_75 , CSI_76 , CSI_77 , CSI_78 , CSI_79 , CSI_7A , CSI_7B , CSI_7C , CSI_7D , CSI_7E , CSI_7F }; struct madtty_t { int pty; pid_t childpid; /* flags */ unsigned seen_input : 1; unsigned insert : 1; unsigned escaped : 1; unsigned graphmode : 1; unsigned curshid : 1; unsigned curskeymode: 1; unsigned bell : 1; /* geometry */ int rows, cols, maxcols; unsigned curattrs; short curfg, curbg; /* scrollback buffer */ struct t_row_t *scroll_buf; int scroll_buf_sz; int scroll_buf_ptr; int scroll_buf_len; int scroll_amount; struct t_row_t *lines; struct t_row_t *scroll_top; struct t_row_t *scroll_bot; /* cursor */ struct t_row_t *curs_row; int curs_col, curs_srow, curs_scol; /* buffers and parsing state */ mbstate_t ps; char rbuf[BUFSIZ]; char ebuf[BUFSIZ]; int rlen, elen; /* custom escape sequence handler */ madtty_handler_t handler; void *data; }; typedef struct t_row_t { wchar_t *text; uint16_t *attr; short *fg; short *bg; unsigned dirty : 1; } t_row_t; static char const * const keytable[KEY_MAX+1] = { ['\n'] = "\r", /* for the arrow keys the CSI / SS3 sequences are not stored here * because they depend on the current cursor terminal mode */ [KEY_UP] = "A", [KEY_DOWN] = "B", [KEY_RIGHT] = "C", [KEY_LEFT] = "D", #ifdef KEY_SUP [KEY_SUP] = "\e[1;2A", #endif #ifdef KEY_SDOWN [KEY_SDOWN] = "\e[1;2B", #endif [KEY_SRIGHT] = "\e[1;2C", [KEY_SLEFT] = "\e[1;2D", [KEY_BACKSPACE] = "\177", [KEY_IC] = "\e[2~", [KEY_DC] = "\e[3~", [KEY_PPAGE] = "\e[5~", [KEY_NPAGE] = "\e[6~", [KEY_HOME] = "\e[7~", [KEY_END] = "\e[8~", [KEY_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[25~", [KEY_F(14)] = "\e[26~", [KEY_F(15)] = "\e[28~", [KEY_F(16)] = "\e[29~", [KEY_F(17)] = "\e[31~", [KEY_F(18)] = "\e[32~", [KEY_F(19)] = "\e[33~", [KEY_F(20)] = "\e[34~", }; __attribute__((const)) static uint16_t build_attrs(unsigned curattrs) { return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff)) >> NCURSES_ATTR_SHIFT; } static void t_row_set(t_row_t *row, int start, int len, madtty_t *t) { row->dirty = true; wmemset(row->text + start, 0, len); attr_t attr = t ? build_attrs(t->curattrs) : 0; short fg = t ? t->curfg : -1; short bg = t ? t->curbg : -1; for (int i = start; i < len + start; i++) { row->attr[i] = attr; row->fg [i] = fg; row->bg [i] = bg; } } static void t_row_roll(t_row_t *start, t_row_t *end, int count) { int n = end - start; count %= n; if (count < 0) count += n; if (count) { t_row_t *buf = alloca(count * sizeof(t_row_t)); memcpy(buf, start, count * sizeof(t_row_t)); memmove(start, start + count, (n - count) * sizeof(t_row_t)); memcpy(end - count, buf, count * sizeof(t_row_t)); for (t_row_t *row = start; row < end; row++) { row->dirty = true; } } } static void clamp_cursor_to_bounds(madtty_t *t) { if (t->curs_row < t->lines) { t->curs_row = t->lines; } if (t->curs_row >= t->lines + t->rows) { t->curs_row = t->lines + t->rows - 1; } if (t->curs_col < 0) { t->curs_col = 0; } if (t->curs_col >= t->cols) { t->curs_col = t->cols - 1; } } static void fill_scroll_buf(madtty_t *t, int s) { /* work in screenfuls */ int ssz = t->scroll_bot - t->scroll_top; if (s > ssz) { fill_scroll_buf(t, ssz); fill_scroll_buf(t, s-ssz); return; } if (s < -ssz) { fill_scroll_buf(t, -ssz); fill_scroll_buf(t, s + ssz); return; } t->scroll_buf_len += s; if (t->scroll_buf_len >= t->scroll_buf_sz) t->scroll_buf_len = t->scroll_buf_sz; if (s > 0 && t->scroll_buf_sz) { for (int i = 0; i < s; i++) { struct t_row_t tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; t->scroll_buf[t->scroll_buf_ptr] = tmp; t->scroll_buf_ptr++; if (t->scroll_buf_ptr == t->scroll_buf_sz) t->scroll_buf_ptr = 0; } } t_row_roll(t->scroll_top, t->scroll_bot, s); if (s < 0 && t->scroll_buf_sz) { for (int i = (-s)-1; i >= 0; i--) { t->scroll_buf_ptr--; if (t->scroll_buf_ptr == -1) t->scroll_buf_ptr = t->scroll_buf_sz - 1; struct t_row_t tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; t->scroll_buf[t->scroll_buf_ptr] = tmp; t->scroll_top[i].dirty = true; } } } static void cursor_line_down(madtty_t *t) { t_row_set(t->curs_row, t->cols, t->maxcols - t->cols, 0); t->curs_row++; if (t->curs_row < t->scroll_bot) return; madtty_noscroll(t); t->curs_row = t->scroll_bot - 1; fill_scroll_buf(t, 1); t_row_set(t->curs_row, 0, t->cols, t); } static void new_escape_sequence(madtty_t *t) { t->escaped = true; t->elen = 0; t->ebuf[0] = '\0'; } static void cancel_escape_sequence(madtty_t *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(madtty_t *t, int param[], int pcount) { int i; if (pcount == 0) { /* special case: reset attributes */ t->curattrs = A_NORMAL; t->curfg = t->curbg = -1; return; } for (i = 0; i < pcount; i++) { switch (param[i]) { #define CASE(x, op) case x: op; break CASE(0, t->curattrs = A_NORMAL; t->curfg = t->curbg = -1); CASE(1, t->curattrs |= A_BOLD); CASE(4, t->curattrs |= A_UNDERLINE); CASE(5, t->curattrs |= A_BLINK); CASE(6, t->curattrs |= A_BLINK); CASE(7, t->curattrs |= A_REVERSE); CASE(8, t->curattrs |= A_INVIS); CASE(22, t->curattrs &= ~A_BOLD); CASE(24, t->curattrs &= ~A_UNDERLINE); CASE(25, t->curattrs &= ~A_BLINK); CASE(27, t->curattrs &= ~A_REVERSE); CASE(28, t->curattrs &= ~A_INVIS); case 30 ... 37: /* fg */ t->curfg = param[i]-30; break; case 38: assert(param[i+1] == 5); t->curfg = param[i+2]; i+=2; break; case 39: t->curfg = -1; break; case 40 ... 47: /* bg */ t->curbg = param[i] - 40; break; case 48: assert(param[i+1] == 5); t->curbg = param[i+2]; i+=2; break; case 49: t->curbg = -1; break; default: break; } } } /* interprets an 'erase display' (ED) escape sequence */ static void interpret_csi_ED(madtty_t *t, int param[], int pcount) { t_row_t *row, *start, *end; /* decide range */ if (pcount && param[0] == 2) { start = t->lines; end = t->lines + t->rows; } else if (pcount && param[0] == 1) { start = t->lines; end = t->curs_row; t_row_set(t->curs_row, 0, t->curs_col + 1, t); } else { t_row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t); start = t->curs_row + 1; end = t->lines + t->rows; } for (row = start; row < end; row++) { t_row_set(row, 0, t->cols, t); } } /* interprets a 'move cursor' (CUP) escape sequence */ static void interpret_csi_CUP(madtty_t *t, int param[], int pcount) { if (pcount == 0) { /* special case */ t->curs_row = t->lines; t->curs_col = 0; return; } else if (pcount < 2) { return; /* malformed */ } t->curs_row = t->lines + param[0] - 1; t->curs_col = param[1] - 1; clamp_cursor_to_bounds(t); } /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, * CPL, CHA, HPR, VPA, VPR, HPA */ static void interpret_csi_C(madtty_t *t, char verb, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; switch (verb) { case 'A': t->curs_row -= n; break; case 'B': case 'e': t->curs_row += n; break; case 'C': case 'a': t->curs_col += n; break; case 'D': t->curs_col -= n; break; case 'E': t->curs_row += n; t->curs_col = 0; break; case 'F': t->curs_row -= n; t->curs_col = 0; break; case 'G': case '`': t->curs_col = param[0] - 1; break; case 'd': t->curs_row = t->lines + param[0] - 1; break; } clamp_cursor_to_bounds(t); } /* Interpret the 'erase line' escape sequence */ static void interpret_csi_EL(madtty_t *t, int param[], int pcount) { switch (pcount ? param[0] : 0) { case 1: t_row_set(t->curs_row, 0, t->curs_col + 1, t); break; case 2: t_row_set(t->curs_row, 0, t->cols, t); break; default: t_row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t); break; } } /* Interpret the 'insert blanks' sequence (ICH) */ static void interpret_csi_ICH(madtty_t *t, int param[], int pcount) { t_row_t *row = t->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; int i; if (t->curs_col + n > t->cols) { n = t->cols - t->curs_col; } for (i = t->cols - 1; i >= t->curs_col + n; i--) { row->text[i] = row->text[i - n]; row->attr[i] = row->attr[i - n]; row->bg [i] = row->bg [i - n]; row->fg [i] = row->fg [i - n]; } t_row_set(row, t->curs_col, n, t); } /* Interpret the 'delete chars' sequence (DCH) */ static void interpret_csi_DCH(madtty_t *t, int param[], int pcount) { t_row_t *row = t->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; int i; if (t->curs_col + n > t->cols) { n = t->cols - t->curs_col; } for (i = t->curs_col; i < t->cols - n; i++) { row->text[i] = row->text[i + n]; row->attr[i] = row->attr[i + n]; row->bg [i] = row->bg [i + n]; row->fg [i] = row->fg [i + n]; } t_row_set(row, t->cols - n, n, t); } /* Interpret a 'scroll reverse' (SR) */ static void interpret_csi_SR(madtty_t *t) { t_row_roll(t->scroll_top, t->scroll_bot, -1); t_row_set(t->scroll_top, 0, t->cols, t); } /* Interpret an 'insert line' sequence (IL) */ static void interpret_csi_IL(madtty_t *t, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; if (t->curs_row + n >= t->scroll_bot) { for (t_row_t *row = t->curs_row; row < t->scroll_bot; row++) { t_row_set(row, 0, t->cols, t); } } else { t_row_roll(t->curs_row, t->scroll_bot, -n); for (t_row_t *row = t->curs_row; row < t->curs_row + n; row++) { t_row_set(row, 0, t->cols, t); } } } /* Interpret a 'delete line' sequence (DL) */ static void interpret_csi_DL(madtty_t *t, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; if (t->curs_row + n >= t->scroll_bot) { for (t_row_t *row = t->curs_row; row < t->scroll_bot; row++) { t_row_set(row, 0, t->cols, t); } } else { t_row_roll(t->curs_row, t->scroll_bot, n); for (t_row_t *row = t->scroll_bot - n; row < t->scroll_bot; row++) { t_row_set(row, 0, t->cols, t); } } } /* Interpret an 'erase characters' (ECH) sequence */ static void interpret_csi_ECH(madtty_t *t, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; if (t->curs_col + n < t->cols) { n = t->cols - t->curs_col; } t_row_set(t->curs_row, t->curs_col, n, t); } /* Interpret a 'set scrolling region' (DECSTBM) sequence */ static void interpret_csi_DECSTBM(madtty_t *t, int param[], int pcount) { int new_top, new_bot; switch (pcount) { case 0: t->scroll_top = t->lines; t->scroll_bot = t->lines + t->rows; break; default: return; /* malformed */ case 2: new_top = param[0] - 1; new_bot = param[1]; /* clamp to bounds */ if (new_top < 0) new_top = 0; if (new_top >= t->rows) new_top = t->rows - 1; if (new_bot < 0) new_bot = 0; if (new_bot >= t->rows) new_bot = t->rows; /* check for range validity */ if (new_top < new_bot) { t->scroll_top = t->lines + new_top; t->scroll_bot = t->lines + new_bot; } break; } } static void es_interpret_csi(madtty_t *t) { static int csiparam[BUFSIZ]; int param_count = 0; const char *p = t->ebuf + 1; char verb = t->ebuf[t->elen - 1]; p += t->ebuf[1] == '?'; /* CSI private mode */ /* parse numeric parameters */ while (isdigit((unsigned char)*p) || *p == ';') { if (*p == ';') { if (param_count >= (int)sizeof(csiparam)) return; /* too long! */ csiparam[param_count++] = 0; } else { if (param_count == 0) csiparam[param_count++] = 0; csiparam[param_count - 1] *= 10; csiparam[param_count - 1] += *p - '0'; } p++; } if (t->ebuf[1] == '?') { switch (verb) { case 'l': if (csiparam[0] == 25) t->curshid = true; if (csiparam[0] == 1) /* DECCKM: reset ANSI cursor (normal) key mode */ t->curskeymode = 0; if (csiparam[0] == 47) { /* use normal screen buffer */ t->curattrs = A_NORMAL; t->curfg = t->curbg = -1; } break; case 'h': if (csiparam[0] == 25) t->curshid = false; if (csiparam[0] == 1) /* DECCKM: set ANSI cursor (application) key mode */ t->curskeymode = 1; if (csiparam[0] == 47) { /* use alternate screen buffer */ t->curattrs = A_NORMAL; t->curfg = t->curbg = -1; } break; } } /* delegate handling depending on command character (verb) */ switch (verb) { case 'h': if (param_count == 1 && csiparam[0] == 4) /* insert mode */ t->insert = true; break; case 'l': if (param_count == 1 && csiparam[0] == 4) /* replace mode */ t->insert = false; break; case 'm': /* it's a 'set attribute' sequence */ interpret_csi_SGR(t, csiparam, param_count); break; case 'J': /* it's an 'erase display' sequence */ interpret_csi_ED(t, csiparam, param_count); break; case 'H': case 'f': /* it's a 'move cursor' sequence */ 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 '`': /* it is a '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 'r': /* set scrolling region */ interpret_csi_DECSTBM(t, csiparam, param_count); break; case 's': /* save cursor location */ t->curs_srow = t->curs_row - t->lines; t->curs_scol = t->curs_col; break; case 'u': /* restore cursor location */ t->curs_row = t->lines + t->curs_srow; t->curs_col = t->curs_scol; clamp_cursor_to_bounds(t); break; default: break; } } static void try_interpret_escape_seq(madtty_t *t) { char lastchar = t->ebuf[t->elen-1]; if(!*t->ebuf) return; if(t->handler){ switch((*(t->handler))(t, t->ebuf)){ case MADTTY_HANDLER_OK: goto cancel; case MADTTY_HANDLER_NOTYET: if (t->elen + 1 >= (int)sizeof(t->ebuf)) goto cancel; return; } } switch (*t->ebuf) { case 'M': interpret_csi_SR(t); cancel_escape_sequence(t); return; case '(': case ')': if (t->elen == 2) goto cancel; break; case ']': /* xterm thing */ if (lastchar == '\a') goto cancel; break; default: goto cancel; case '[': if (is_valid_csi_ender(lastchar)) { es_interpret_csi(t); cancel_escape_sequence(t); return; } break; } if (t->elen + 1 >= (int)sizeof(t->ebuf)) { cancel: #ifndef NDEBUG fprintf(stderr, "cancelled: \\033"); for (int i = 0; i < (int)t->elen; i++) { if (isprint(t->ebuf[i])) { fputc(t->ebuf[i], stderr); } else { fprintf(stderr, "\\%03o", t->ebuf[i]); } } fputc('\n', stderr); #endif cancel_escape_sequence(t); } } static void madtty_process_nonprinting(madtty_t *t, wchar_t wc) { switch (wc) { case C0_ESC: new_escape_sequence(t); break; case C0_BEL: /* maybe a visual bell would be nice? */ if(t->bell) beep(); break; case C0_BS: if (t->curs_col > 0) t->curs_col--; break; case C0_HT: /* tab */ t->curs_col = (t->curs_col + 8) & ~7; if (t->curs_col >= t->cols) t->curs_col = t->cols - 1; break; case C0_CR: t->curs_col = 0; break; case C0_VT: case C0_FF: case C0_LF: cursor_line_down(t); break; case C0_SO: /* shift out - acs */ t->graphmode = true; break; case C0_SI: /* shift in - acs */ t->graphmode = false; break; } } static void is_utf8_locale(void) { const char *cset = nl_langinfo(CODESET) ?: "ANSI_X3.4-1968"; is_utf8 = !strcmp(cset, "UTF-8"); } // vt100 special graphics and line drawing // 5f-7e standard vt100 // 40-5e rxvt extension for extra curses acs chars static uint16_t const vt100_utf8[62] = { // 41 .. 7e 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 hi mr. snowman! 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 }; static uint32_t vt100[62]; void madtty_init_vt100_graphics(void) { vt100['l' - 0x41] = ACS_ULCORNER; vt100['m' - 0x41] = ACS_LLCORNER; vt100['k' - 0x41] = ACS_URCORNER; vt100['j' - 0x41] = ACS_LRCORNER; vt100['u' - 0x41] = ACS_RTEE; vt100['t' - 0x41] = ACS_LTEE; vt100['v' - 0x41] = ACS_TTEE; vt100['w' - 0x41] = ACS_BTEE; vt100['q' - 0x41] = ACS_HLINE; vt100['x' - 0x41] = ACS_VLINE; vt100['n' - 0x41] = ACS_PLUS; vt100['o' - 0x41] = ACS_S1; vt100['s' - 0x41] = ACS_S9; vt100['`' - 0x41] = ACS_DIAMOND; vt100['a' - 0x41] = ACS_CKBOARD; vt100['f' - 0x41] = ACS_DEGREE; vt100['g' - 0x41] = ACS_PLMINUS; vt100['~' - 0x41] = ACS_BULLET; #if 0 /* out of bounds */ vt100[',' - 0x41] = ACS_LARROW; vt100['+' - 0x41] = ACS_RARROW; vt100['.' - 0x41] = ACS_DARROW; vt100['-' - 0x41] = ACS_UARROW; vt100['0' - 0x41] = ACS_BLOCK; #endif vt100['h' - 0x41] = ACS_BOARD; vt100['i' - 0x41] = ACS_LANTERN; /* these defaults were invented for ncurses */ vt100['p' - 0x41] = ACS_S3; vt100['r' - 0x41] = ACS_S7; vt100['y' - 0x41] = ACS_LEQUAL; vt100['z' - 0x41] = ACS_GEQUAL; vt100['{' - 0x41] = ACS_PI; vt100['|' - 0x41] = ACS_NEQUAL; vt100['}' - 0x41] = ACS_STERLING; is_utf8_locale(); } static void madtty_putc(madtty_t *t, wchar_t wc) { int width = 0; if (!t->seen_input) { t->seen_input = 1; kill(-t->childpid, SIGWINCH); } if (t->escaped) { assert (t->elen + 1 < (int)sizeof(t->ebuf)); t->ebuf[t->elen] = wc; t->ebuf[++t->elen] = '\0'; try_interpret_escape_seq(t); } else if (IS_CONTROL(wc)) { madtty_process_nonprinting(t, wc); } else { t_row_t *tmp; if (t->graphmode) { if (wc >= 0x41 && wc <= 0x7e) { wchar_t gc = is_utf8 ? vt100_utf8[wc - 0x41] : vt100[wc - 0x41]; if (gc) wc = gc; } width = 1; } else { width = wcwidth(wc) ?: 1; } if (width == 2 && t->curs_col == t->cols - 1) { tmp = t->curs_row; tmp->dirty = true; tmp->text[t->curs_col] = 0; tmp->attr[t->curs_col] = build_attrs(t->curattrs); tmp->bg[t->curs_col] = t->curbg; tmp->fg[t->curs_col] = t->curfg; t->curs_col++; } if (t->curs_col >= t->cols) { t->curs_col = 0; cursor_line_down(t); } tmp = t->curs_row; tmp->dirty = true; if (t->insert) { wmemmove(tmp->text + t->curs_col + width, tmp->text + t->curs_col, (t->cols - t->curs_col - width)); memmove(tmp->attr + t->curs_col + width, tmp->attr + t->curs_col, (t->cols - t->curs_col - width) * sizeof(tmp->attr[0])); memmove(tmp->fg + t->curs_col + width, tmp->fg + t->curs_col, (t->cols - t->curs_col - width) * sizeof(tmp->fg[0])); memmove(tmp->bg + t->curs_col + width, tmp->bg + t->curs_col, (t->cols - t->curs_col - width) * sizeof(tmp->bg[0])); } tmp->text[t->curs_col] = wc; tmp->attr[t->curs_col] = build_attrs(t->curattrs); tmp->bg[t->curs_col] = t->curbg; tmp->fg[t->curs_col] = t->curfg; t->curs_col++; if (width == 2) { tmp->text[t->curs_col] = 0; tmp->attr[t->curs_col] = build_attrs(t->curattrs); tmp->bg[t->curs_col] = t->curbg; tmp->fg[t->curs_col] = t->curfg; t->curs_col++; } } } int madtty_process(madtty_t *t) { int res, pos = 0; 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, &t->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; madtty_putc(t, wc); } t->rlen -= pos; memmove(t->rbuf, t->rbuf + pos, t->rlen); return 0; } madtty_t *madtty_create(int rows, int cols, int scroll_buf_sz) { madtty_t *t; int i; if (rows <= 0 || cols <= 0) return NULL; t = (madtty_t*)calloc(sizeof(madtty_t), 1); if (!t) return NULL; /* record dimensions */ t->rows = rows; t->cols = cols; t->maxcols = cols; /* default mode is replace */ t->insert = false; /* create the cell matrix */ t->lines = (t_row_t*)calloc(sizeof(t_row_t), t->rows); for (i = 0; i < t->rows; i++) { t->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), t->cols); t->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), t->cols); t->lines[i].fg = calloc(sizeof(short), t->cols); t->lines[i].bg = calloc(sizeof(short), t->cols); } t->pty = -1; /* no pty for now */ /* initialization of other public fields */ t->curs_row = t->lines; t->curs_col = 0; t->curattrs = A_NORMAL; /* white text over black background */ t->curfg = t->curbg = -1; /* initial scrolling area is the whole window */ t->scroll_top = t->lines; t->scroll_bot = t->lines + t->rows; /* scrollback buffer */ if (scroll_buf_sz < 0) scroll_buf_sz = 0; t->scroll_buf_sz = scroll_buf_sz; t->scroll_buf = calloc(sizeof(t_row_t), t->scroll_buf_sz); for (i = 0; i < t->scroll_buf_sz; i++) { t->scroll_buf[i].text = calloc(sizeof(wchar_t), t->cols); t->scroll_buf[i].attr = calloc(sizeof(uint16_t), t->cols); t->scroll_buf[i].fg = calloc(sizeof(short), t->cols); t->scroll_buf[i].bg = calloc(sizeof(short), t->cols); } t->scroll_buf_ptr = t->scroll_buf_len = 0; t->scroll_amount = 0; return t; } void madtty_resize(madtty_t *t, int rows, int cols) { struct winsize ws = { .ws_row = rows, .ws_col = cols }; t_row_t *lines = t->lines; if (rows <= 0 || cols <= 0) return; madtty_noscroll(t); if (t->rows != rows) { if (t->curs_row > lines+rows) { /* scroll up instead of simply chopping off bottom */ fill_scroll_buf(t, t->rows - rows); } while (t->rows > rows) { free(lines[t->rows - 1].text); free(lines[t->rows - 1].attr); t->rows--; } lines = realloc(lines, sizeof(t_row_t) * rows); } if (t->maxcols < cols) { for (int row = 0; row < t->rows; row++) { lines[row].text = realloc(lines[row].text, sizeof(wchar_t) * cols); lines[row].attr = realloc(lines[row].attr, sizeof(uint16_t) * cols); lines[row].fg = realloc(lines[row].fg, sizeof(short) * cols); lines[row].bg = realloc(lines[row].bg, sizeof(short) * cols); if (t->cols < cols) t_row_set(lines + row, t->cols, cols - t->cols, 0); lines[row].dirty = true; } t_row_t *sbuf = t->scroll_buf; for (int row = 0; row < t->scroll_buf_sz; row++) { sbuf[row].text = realloc(sbuf[row].text, sizeof(wchar_t) * cols); sbuf[row].attr = realloc(sbuf[row].attr, sizeof(uint16_t) * cols); sbuf[row].fg = realloc(sbuf[row].fg, sizeof(short) * cols); sbuf[row].bg = realloc(sbuf[row].bg, sizeof(short) * cols); if (t->cols < cols) t_row_set(sbuf + row, t->cols, cols - t->cols, 0); } t->maxcols = cols; t->cols = cols; } else if (t->cols != cols) { madtty_dirty(t); t->cols = cols; } while (t->rows < rows) { lines[t->rows].text = (wchar_t *)calloc(sizeof(wchar_t), t->maxcols); lines[t->rows].attr = (uint16_t *)calloc(sizeof(uint16_t), t->maxcols); lines[t->rows].fg = calloc(sizeof(short), t->maxcols); lines[t->rows].bg = calloc(sizeof(short), t->maxcols); t_row_set(lines + t->rows, 0, t->maxcols, 0); t->rows++; } t->curs_row += lines - t->lines; t->scroll_top = lines; t->scroll_bot = lines + rows; t->lines = lines; clamp_cursor_to_bounds(t); ioctl(t->pty, TIOCSWINSZ, &ws); kill(-t->childpid, SIGWINCH); } void madtty_destroy(madtty_t *t) { int i; if (!t) return; for (i = 0; i < t->rows; i++) { free(t->lines[i].text); free(t->lines[i].attr); free(t->lines[i].fg); free(t->lines[i].bg); } free(t->lines); for (i = 0; i < t->scroll_buf_sz; i++) { free(t->scroll_buf[i].text); free(t->scroll_buf[i].attr); free(t->scroll_buf[i].fg); free(t->scroll_buf[i].bg); } free(t->scroll_buf); free(t); } void madtty_dirty(madtty_t *t) { for (int i = 0; i < t->rows; i++) t->lines[i].dirty = true; } void madtty_draw(madtty_t *t, WINDOW *win, int srow, int scol) { curs_set(0); for (int i = 0; i < t->rows; i++) { t_row_t *row = t->lines + i; if (!row->dirty) continue; wmove(win, srow + i, scol); for (int j = 0; j < t->cols; j++) { if (!j || row->attr[j] != row->attr[j - 1] || row->fg[j] != row->fg[j-1] || row->bg[j] != row->bg[j-1]) { wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT); madtty_color_set(win, row->fg[j], row->bg[j]); } if (is_utf8 && row->text[j] >= 128) { char buf[MB_CUR_MAX + 1]; int len; len = wcrtomb(buf, row->text[j], NULL); waddnstr(win, buf, len); if (wcwidth(row->text[j]) > 1) j++; } else { waddch(win, row->text[j] > ' ' ? row->text[j] : ' '); } } row->dirty = false; } wmove(win, srow + t->curs_row - t->lines, scol + t->curs_col); curs_set(madtty_cursor(t)); } void madtty_scroll(madtty_t *t, int rows) { if (rows < 0) { /* scroll back */ if (rows < -t->scroll_buf_len) rows = -t->scroll_buf_len; } else { /* scroll forward */ if (rows > t->scroll_amount) rows = t->scroll_amount; } fill_scroll_buf(t, rows); t->scroll_amount -= rows; } void madtty_noscroll(madtty_t *t) { if (t->scroll_amount) madtty_scroll(t, t->scroll_amount); } void madtty_bell(madtty_t *t, bool bell) { t->bell = bell; } void madtty_togglebell(madtty_t *t) { t->bell = !t->bell; } /******************************************************/ pid_t madtty_forkpty(madtty_t *t, const char *p, const char *argv[], const char *env[], int *pty) { struct winsize ws; pid_t pid; const char **envp = env; int fd, maxfd; ws.ws_row = t->rows; ws.ws_col = t->cols; ws.ws_xpixel = ws.ws_ypixel = 0; pid = forkpty(&t->pty, NULL, NULL, &ws); if (pid < 0) return -1; if (pid == 0) { setsid(); maxfd = sysconf(_SC_OPEN_MAX); for (fd = 3; fd < maxfd; fd++) if (close(fd) == EBADF) break; while (envp && envp[0]) { setenv(envp[0], envp[1], 1); envp += 2; } setenv("TERM", use_palette ? "rxvt-256color" : "rxvt", 1); execv(p, (char *const*)argv); fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]); exit(1); } if (pty) *pty = t->pty; return t->childpid = pid; } int madtty_getpty(madtty_t *t) { return t->pty; } static void term_write(madtty_t *t, const char *buf, int len) { while (len > 0) { int res = write(t->pty, buf, len); if (res < 0 && errno != EAGAIN && errno != EINTR) return; buf += res; len -= res; } } void madtty_keypress(madtty_t *t, int keycode) { char c = (char)keycode; madtty_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] }; term_write(t, keyseq, 3); break; } default: term_write(t, keytable[keycode], strlen(keytable[keycode])); } } else term_write(t, &c, 1); } void madtty_keypress_sequence(madtty_t *t, const char *seq) { int key, len = strlen(seq); /* check for function keys from putty, this makes the * keypad work but it's probably not the right way to * do it. the sequence we look for is \eO + a character * representing the number. */ if(len == 3 && seq[0] == '\e' && seq[1] == 'O') { key = seq[2] - 64; if(key >= '0' && key <= '9') madtty_keypress(t, key); } else term_write(t, seq, len); } void madtty_color_set(WINDOW *win, short fg, short bg) { if (use_palette) { if (fg == -1 && bg == -1) { wcolor_set(win, 0, 0); } else { unsigned c = color_hash(fg, bg); if (color2palette[c] == 0) { short f, g; color2palette[c] = palette_cur; pair_content(palette_cur, &f, &g); color2palette[color_hash(f,g)] = 0; init_pair(palette_cur, fg, bg); palette_cur++; if (palette_cur >= palette_end) { palette_cur = palette_start; /* possibly use mvwinch/mvchgat to update palette */ } } wcolor_set(win, color2palette[c], 0); } } else { if (has_default) { wcolor_set(win, (fg+1)*16 + bg+1, 0); } else { if (fg==-1) fg = COLOR_WHITE; if (bg==-1) bg = COLOR_BLACK; wcolor_set(win, (7-fg)*8 + bg, 0); } } } void madtty_init_colors(void) { int use_default = use_default_colors() == OK; use_palette = 0; if (COLORS >= 256 && COLOR_PAIRS >= 256) { use_palette = 1; has_default = 1; color2palette = calloc((COLORS+1)*(COLORS+1), sizeof(short)); int bg = 0, fg = 0; for (int i = palette_start; i < palette_end; i++) { init_pair(i, bg, fg); color2palette[color_hash(bg,fg)] = i; if (++fg == COLORS) { fg = 0; bg++; } } palette_cur = palette_start; } else if (COLOR_PAIRS > 64) { has_default = 1; for (int bg = -1; bg < 8; bg++) { for (int fg = -1; fg < 8; fg++) { init_pair((fg + 1) * 16 + bg + 1, fg, bg); } } } else { for (int bg = 0; bg < 8; bg++) { for (int fg = 0; fg < 8; fg++) { if (use_default) { init_pair((7 - fg) * 8 + bg, fg == COLOR_WHITE ? -1 : fg, bg == COLOR_BLACK ? -1 : bg); } else { init_pair((7 - fg) * 8 + bg, fg, bg); } } } } } void madtty_set_handler(madtty_t *t, madtty_handler_t handler) { t->handler = handler; } void madtty_set_data(madtty_t *t, void *data) { t->data = data; } void *madtty_get_data(madtty_t *t) { return t->data; } unsigned madtty_cursor(madtty_t *t) { return t->scroll_amount ? 0 : !t->curshid; } dvtm-0.6/README0000644000175000017500000000220611453640555011647 0ustar marcmarcdvtm - dynamic virtual terminal manager ======================================= dvtm brings dwm and it's concept of tiling window management to the console. See http://www.brain-dump.org/projects/dvtm for the latest version. Requirements ------------ In order to build dvtm you will need: * libncurses or libncursesw for wide character support Installation ------------ Edit config.mk to match your local setup (dvtm is installed into the /usr/local namespace and links against libncursesw by default). Afterwards enter the following command to build and install dvtm (if necessary as root). make && make install Running dvtm ------------ Just run dvtm from the console, redirect stderr to a file (just in case something goes wrong you will see it there). dvtm 2> log If you want to display a one line status bar you can create a named pipe and pass it's name to dvtm via it's -s command line option. Make sure that the pipe remains open until dvtm is closed, see the included dvtm-status script as an example. Configuration ------------- The configuration of dvtm is done by creating a custom config.h and (re)compiling the source code. dvtm-0.6/cmdfifo.c0000644000175000017500000000576211453640555012554 0ustar marcmarcstatic int cmdfd = -1; static unsigned short int client_id = 0; static const char *cmdpath = NULL; /* glibc has a non-standard realpath(3) implementation which allocates * the destination buffer, other C libraries may have a broken implementation * which expect an already allocated destination buffer. */ #ifndef __GLIBC__ # include # ifndef PATH_MAX # define PATH_MAX 1024 # endif #endif static char *get_realpath(const char *path) { #ifdef __GLIBC__ return realpath(path, NULL); #else static char buf[PATH_MAX]; return realpath(path, buf); #endif } static Cmd * get_cmd_by_name(const char *name) { for (int i = 0; i < countof(commands); i++) { if (!strcmp(name, commands[i].name)) return &commands[i]; } return NULL; } static void handle_cmdfifo() { int r; char *p, *s, cmdbuf[512], c; Cmd *cmd; switch (r = read(cmdfd, cmdbuf, sizeof cmdbuf - 1)) { case -1: case 0: cmdfd = -1; break; default: cmdbuf[r] = '\0'; p = cmdbuf; while (*p) { /* find the command name */ for (; *p == ' ' || *p == '\n'; p++); for (s = p; *p && *p != ' ' && *p != '\n'; p++); if ((c = *p)) *p++ = '\0'; if (*s && (cmd = get_cmd_by_name(s)) != NULL) { bool quote = false; int argc = 0; /* XXX: initializer assumes MAX_ARGS == 2 use a initialization loop? */ const char *args[MAX_ARGS] = { NULL, NULL}, *arg; /* if arguments were specified in config.h ignore the one given via * the named pipe and thus skip everything until we find a new line */ if (cmd->action.args[0] || c == '\n') { debug("execute %s", s); cmd->action.cmd(cmd->action.args); while (*p && *p != '\n') p++; continue; } /* no arguments were given in config.h so we parse the command line */ while (*p == ' ') p++; arg = p; for (; (c = *p); p++) { switch (*p) { case '\\': /* remove the escape character '\\' move every * following character to the left by one position */ switch (*(++p)) { case '\\': case '\'': case '\"': { char *t = p; for (;;) { *(t - 1) = *t; if (*t++ == '\0') break; } p -= 2; } } 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') { debug("execute %s", s); for(int i = 0; i < argc; i++) debug(" %s", args[i]); debug("\n"); cmd->action.cmd(args); break; } } } } } } dvtm-0.6/dvtm.c0000644000175000017500000005162311453640555012114 0ustar marcmarc/* * The initial "port" of dwm to curses was done by * (c) 2007-2009 Marc Andre Tanner * * It is highly inspired by the original X11 dwm and * reuses some code of it which is mostly * * (c) 2006-2007 Anselm R. Garbe * * See LICENSE for details. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ # include #endif #include "madtty.h" typedef struct { const char *symbol; void (*arrange)(void); } Layout; typedef struct Client Client; struct Client { WINDOW *window; madtty_t *term; const char *cmd; char title[256]; uint8_t order; pid_t pid; int pty; #ifdef CONFIG_CMDFIFO unsigned short int id; #endif short int x; short int y; short int w; short int h; bool minimized; bool died; Client *next; Client *prev; }; #define ALT(k) ((k) + (161 - 'a')) #ifndef CTRL #define CTRL(k) ((k) & 0x1F) #endif #define CTRL_ALT(k) ((k) + (129 - 'a')) #define MAX_ARGS 2 typedef struct { void (*cmd)(const char *args[]); /* needed to avoid an error about initialization * of nested flexible array members */ const char *args[MAX_ARGS + 1]; } Action; typedef struct { unsigned int mod; unsigned int code; Action action; } Key; #ifdef CONFIG_MOUSE typedef struct { mmask_t mask; Action action; } Button; #endif #ifdef CONFIG_CMDFIFO typedef struct { const char *name; Action action; } Cmd; #endif #ifdef CONFIG_STATUSBAR enum { BarTop, BarBot, BarOff }; #endif #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) #define sstrlen(str) (sizeof(str) - 1) #define max(x, y) ((x) > (y) ? (x) : (y)) #ifdef NDEBUG #define debug(format, args...) #else #define debug eprint #endif /* commands for use by keybindings */ static void quit(const char *args[]); static void create(const char *args[]); static void startup(const char *args[]); static void escapekey(const char *args[]); static void killclient(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 togglebell(const char *key[]); static void toggleminimize(const char *args[]); static void setmwfact(const char *args[]); static void setlayout(const char *args[]); static void scrollback(const char *args[]); static void redraw(const char *args[]); static void zoom(const char *args[]); static void lock(const char *key[]); static void togglerunall(const char *args[]); #ifdef CONFIG_STATUSBAR enum { ALIGN_LEFT, ALIGN_RIGHT }; static void togglebar(const char *args[]); #endif #ifdef CONFIG_MOUSE 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[]); static void mouse_toggle(); #endif static void clear_workspace(); static void draw(Client *c); static void draw_all(bool border); static void draw_border(Client *c); static void resize(Client *c, int x, int y, int w, int h); static void resize_screen(); static void eprint(const char *errstr, ...); static bool isarrange(void (*func)()); static void arrange(); static void focus(Client *c); static void keypress(int code); static unsigned int waw, wah, wax, way; static Client *clients = NULL; extern double mwfact; #include "config.h" static Client *sel = NULL; double mwfact = MWFACT; static Layout *layout = layouts; static const char *shell; static bool need_screen_resize; static int width, height, scroll_buf_size = SCROLL_BUF_SIZE; static bool running = true; static bool runinall = false; #ifdef CONFIG_MOUSE # include "mouse.c" #endif #ifdef CONFIG_CMDFIFO # include "cmdfifo.c" #endif #ifdef CONFIG_STATUSBAR # include "statusbar.c" #endif 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 void attach(Client *c) { uint8_t order; if (clients) clients->prev = c; c->next = clients; c->prev = NULL; clients = c; for (order = 1; c; c = c->next, order++) c->order = order; } static void attachafter(Client *c, Client *a) { /* attach c after a */ uint8_t o; 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 (o = a->order; c; c = c->next) c->order = ++o; } } static void detach(Client *c) { Client *d; if (c->prev) c->prev->next = c->next; if (c->next) { c->next->prev = c->prev; for (d = c->next; d; d = d->next) --d->order; } if (c == clients) clients = c->next; c->next = c->prev = NULL; } static void arrange() { clear_workspace(); layout->arrange(); wnoutrefresh(stdscr); draw_all(true); } static bool isarrange(void (*func)()) { return func == layout->arrange; } static void focus(Client *c) { Client *tmp = sel; if (sel == c) return; sel = c; if (tmp) { draw_border(tmp); wrefresh(tmp->window); } if (isarrange(fullscreen)) redrawwin(c->window); draw_border(c); wrefresh(c->window); } static void focusn(const char *args[]) { Client *c; for (c = clients; c; c = c->next) { if (c->order == atoi(args[0])) { focus(c); if (c->minimized) toggleminimize(NULL); return; } } } static void focusnext(const char *args[]) { Client *c; if (!sel) return; c = sel->next; if (!c) c = clients; if (c) focus(c); } static void focusnextnm(const char *args[]) { Client *c; if (!sel) return; c = sel; do { c = c->next; if (!c) c = clients; } while (c->minimized && c != sel); focus(c); } static void focusprev(const char *args[]) { Client *c; if (!sel) return; c = sel->prev; if (!c) for (c = clients; c && c->next; c = c->next); if (c) focus(c); } static void focusprevnm(const char *args[]) { Client *c; if (!sel) return; c = sel; do { c = c->prev; if (!c) for (c = clients; c && c->next; c = c->next); } while (c->minimized && c != sel); focus(c); } static void zoom(const char *args[]) { Client *c; if (!sel) return; if ((c = sel) == clients) if (!(c = c->next)) return; detach(c); attach(c); focus(c); if (c->minimized) toggleminimize(NULL); arrange(); } static void togglebell(const char *args[]) { madtty_togglebell(sel->term); } static void toggleminimize(const char *args[]) { Client *c, *m; unsigned int n; if (!sel) return; /* the last window can't be minimized */ if (!sel->minimized) { for (n = 0, c = clients; c; c = c->next) if (!c->minimized) n++; if (n == 1) return; } sel->minimized = !sel->minimized; m = sel; /* check whether the master client was minimized */ if (sel == clients && sel->minimized) { c = sel->next; detach(c); attach(c); focus(c); detach(m); for (; c && c->next && !c->next->minimized; c = c->next); attachafter(m, c); } else if (m->minimized) { /* non master window got minimized move it above all other * minimized ones */ focusnextnm(NULL); detach(m); for (c = clients; c && c->next && !c->next->minimized; c = c->next); attachafter(m, c); } else { /* window is no longer minimized, move it to the master area */ madtty_dirty(m->term); detach(m); attach(m); } arrange(); } static void setlayout(const char *args[]) { unsigned int i; if (!args || !args[0]) { if (++layout == &layouts[countof(layouts)]) layout = &layouts[0]; } else { for (i = 0; i < countof(layouts); i++) if (!strcmp(args[0], layouts[i].symbol)) break; if (i == countof(layouts)) return; layout = &layouts[i]; } arrange(); } static void setmwfact(const char *args[]) { double delta; if (isarrange(fullscreen) || isarrange(grid)) return; /* arg handling, manipulate mwfact */ if (args[0] == NULL) mwfact = MWFACT; else if (1 == sscanf(args[0], "%lf", &delta)) { if (args[0][0] == '+' || args[0][0] == '-') mwfact += delta; else mwfact = delta; if (mwfact < 0.1) mwfact = 0.1; else if (mwfact > 0.9) mwfact = 0.9; } arrange(); } static void scrollback(const char *args[]) { if (!sel) return; if (!args[0] || atoi(args[0]) < 0) madtty_scroll(sel->term, -sel->h/2); else madtty_scroll(sel->term, sel->h/2); draw(sel); } static void redraw(const char *args[]) { wrefresh(curscr); resize_screen(); draw_all(true); } static void draw_border(Client *c) { char *s, t = '\0'; int x, y, o; if (sel == c) { wattrset(c->window, SELECTED_ATTR); madtty_color_set(c->window, SELECTED_FG, SELECTED_BG); } else { wattrset(c->window, NORMAL_ATTR); madtty_color_set(c->window, NORMAL_FG, NORMAL_BG); } getyx(c->window, y, x); curs_set(0); mvwhline(c->window, 0, 0, ACS_HLINE, c->w); o = c->w - (4 + sstrlen(TITLE) - 5 + sstrlen(SEPARATOR)); if (o < 0) o = 0; if (o < sizeof(c->title)) { t = *(s = &c->title[o]); *s = '\0'; } mvwprintw(c->window, 0, 2, TITLE, *c->title ? c->title : "", *c->title ? SEPARATOR : "", c->order); if (t) *s = t; wmove(c->window, y, x); if (!c->minimized) curs_set(madtty_cursor(c->term)); } static void draw_content(Client *c) { if (!c->minimized || isarrange(fullscreen)) { madtty_draw(c->term, c->window, 1, 0); if (c != sel) curs_set(0); } } static void draw(Client *c) { draw_content(c); draw_border(c); wrefresh(c->window); } static void clear_workspace() { unsigned int y; for (y = 0; y < wah; y++) mvhline(way + y, 0, ' ', waw); wnoutrefresh(stdscr); } static void draw_all(bool border) { Client *c; curs_set(0); for (c = clients; c; c = c->next) { redrawwin(c->window); if (c == sel) continue; draw_content(c); if (border) draw_border(c); wnoutrefresh(c->window); } /* as a last step the selected window is redrawn, * this has the effect that the cursor position is * accurate */ refresh(); if (sel) { draw_content(sel); if (border) draw_border(sel); wrefresh(sel->window); } } static void escapekey(const char *args[]) { int key; if ((key = getch()) >= 0) { debug("escaping key `%c'\n", key); keypress(CTRL(key)); } } /* * Lock the screen until the correct password is entered. * The password can either be specified in config.h which is * not recommended because `strings dvtm` will contain it. If * no password is specified in the configuration file it is read * from the keyboard before the screen is locked. * * NOTE: this function doesn't handle the input from clients. All * foreground operations are temporarily suspended since the * function doesn't return. */ static void lock(const char *args[]) { size_t len = 0, i = 0; char buf[16], *pass = buf, c; erase(); curs_set(0); if (args && args[0]) { len = strlen(args[0]); pass = (char *)args[0]; } else { mvprintw(LINES / 2, COLS / 2 - 7, "Enter password"); while (len < sizeof buf && (c = getch()) != '\n') if (c != ERR) buf[len++] = c; } mvprintw(LINES / 2, COLS / 2 - 7, "Screen locked!"); while (i != len) { for(i = 0; i < len; i++) { if (getch() != pass[i]) break; } } arrange(); } static void togglerunall(const char *args[]) { runinall = !runinall; } static void killclient(const char *args[]) { if (!sel) return; debug("killing client with pid: %d\n", sel->pid); kill(-sel->pid, SIGKILL); } static int title_escape_seq_handler(madtty_t *term, char *es) { Client *c; unsigned int l; if (es[0] != ']' || (es[1] && (es[1] < '0' || es[1] > '9')) || (es[2] && es[2] != ';')) return MADTTY_HANDLER_NOWAY; if ((l = strlen(es)) < 3 || es[l - 1] != '\07') return MADTTY_HANDLER_NOTYET; es[l - 1] = '\0'; c = (Client *)madtty_get_data(term); strncpy(c->title, es + 3, sizeof(c->title)); draw_border(c); debug("window title: %s\n", c->title); return MADTTY_HANDLER_OK; } static void create(const char *args[]) { Client *c = calloc(sizeof(Client), 1); if (!c) return; const char *cmd = (args && args[0]) ? args[0] : shell; const char *pargs[] = { "/bin/sh", "-c", cmd, NULL }; #ifdef CONFIG_CMDFIFO c->id = ++client_id; char buf[8]; snprintf(buf, sizeof buf, "%d", c->id); #endif const char *env[] = { "DVTM", VERSION, #ifdef CONFIG_CMDFIFO "DVTM_WINDOW_ID", buf, #endif NULL }; c->window = newwin(wah, waw, way, wax); c->term = madtty_create(height - 1, width, scroll_buf_size); c->cmd = cmd; if (args && args[1]) strncpy(c->title, args[1], sizeof(c->title)); c->pid = madtty_forkpty(c->term, "/bin/sh", pargs, env, &c->pty); madtty_set_data(c->term, c); madtty_set_handler(c->term, title_escape_seq_handler); c->w = width; c->h = height; c->x = wax; c->y = way; c->order = 0; c->minimized = false; debug("client with pid %d forked\n", c->pid); attach(c); focus(c); arrange(); } static void destroy(Client *c) { if (sel == c) focusnextnm(NULL); detach(c); if (sel == c) { if (clients) { focus(clients); toggleminimize(NULL); } else sel = NULL; } werase(c->window); wrefresh(c->window); madtty_destroy(c->term); delwin(c->window); if (!clients && countof(actions)) { if (!strcmp(c->cmd, shell)) quit(NULL); else create(NULL); } free(c); arrange(); } static void 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) { if (c->w == w && c->h == h) return; 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; } madtty_resize(c->term, h - 1, 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 bool is_modifier(unsigned int mod) { unsigned int i; for (i = 0; i < countof(keys); i++) { if (keys[i].mod == mod) return true; } return false; } static Key* keybinding(unsigned int mod, unsigned int code) { unsigned int i; for (i = 0; i < countof(keys); i++) { if (keys[i].mod == mod && keys[i].code == code) return &keys[i]; } return NULL; } static Client* get_client_by_pid(pid_t pid) { Client *c; for (c = clients; c; c = c->next) { if (c->pid == pid) return c; } return NULL; } static void sigchld_handler(int sig) { int errsv = errno; int status; pid_t pid; Client *c; signal(SIGCHLD, sigchld_handler); 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); if ((c = get_client_by_pid(pid))) c->died = true; } errno = errsv; } static void sigwinch_handler(int sig) { signal(SIGWINCH, sigwinch_handler); need_screen_resize = true; } static void sigterm_handler(int sig) { running = false; } static void resize_screen() { struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) == -1) return; width = ws.ws_col; height = ws.ws_row; debug("resize_screen(), w: %d h: %d\n", width, height); #if defined(__OpenBSD__) || defined(__NetBSD__) resizeterm(height, width); #else resize_term(height, width); #endif wresize(stdscr, height, width); wrefresh(curscr); refresh(); waw = width; wah = height; #ifdef CONFIG_STATUSBAR updatebarpos(); drawbar(); #endif arrange(); } static void startup(const char *args[]) { for (int i = 0; i < countof(actions); i++) actions[i].cmd(actions[i].args); } static void setup() { if (!(shell = getenv("SHELL"))) shell = "/bin/sh"; setlocale(LC_CTYPE, ""); initscr(); start_color(); noecho(); keypad(stdscr, TRUE); #ifdef CONFIG_MOUSE mouse_setup(); #endif raw(); madtty_init_colors(); madtty_init_vt100_graphics(); getmaxyx(stdscr, height, width); resize_screen(); signal(SIGWINCH, sigwinch_handler); signal(SIGCHLD, sigchld_handler); signal(SIGTERM, sigterm_handler); } static void cleanup() { endwin(); #ifdef CONFIG_STATUSBAR if (statusfd > 0) close(statusfd); #endif #ifdef CONFIG_CMDFIFO if (cmdfd > 0) close(cmdfd); if (cmdpath) unlink(cmdpath); #endif } static void quit(const char *args[]) { cleanup(); exit(EXIT_SUCCESS); } static void usage() { cleanup(); eprint("usage: dvtm [-v] [-m mod] [-d escdelay] [-h n] " #ifdef CONFIG_STATUSBAR "[-s status-fifo] " #endif #ifdef CONFIG_CMDFIFO "[-c cmd-fifo] " #endif "[cmd...]\n"); exit(EXIT_FAILURE); } static int open_or_create_fifo(const char *name) { struct stat info; int fd; open: if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) { if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) goto open; error("%s\n", strerror(errno)); } 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 bool parse_args(int argc, char *argv[]) { int arg; bool init = false; if (!getenv("ESCDELAY")) ESCDELAY = 100; for (arg = 1; arg < argc; arg++) { if (argv[arg][0] != '-') { const char *args[] = { argv[arg], NULL }; if (!init) { setup(); init = true; } create(args); continue; } if (argv[arg][1] != 'v' && (arg + 1) >= argc) usage(); switch (argv[arg][1]) { case 'v': puts("dvtm-"VERSION" (c) 2007-2009 Marc Andre Tanner"); exit(EXIT_SUCCESS); case 'm': { char *mod = argv[++arg]; if (mod[0] == '^' && mod[1]) *mod = CTRL(mod[1]); for (int i = 0; i < countof(keys); i++) keys[i].mod = *mod; break; } case 'd': ESCDELAY = atoi(argv[++arg]); if (ESCDELAY < 50) ESCDELAY = 50; else if (ESCDELAY > 1000) ESCDELAY = 1000; break; case 'h': scroll_buf_size = atoi(argv[++arg]); break; #ifdef CONFIG_STATUSBAR case 's': statusfd = open_or_create_fifo(argv[++arg]); updatebarpos(); break; #endif #ifdef CONFIG_CMDFIFO case 'c': cmdfd = open_or_create_fifo(argv[++arg]); if (!(cmdpath = get_realpath(argv[arg]))) error("%s\n", strerror(errno)); setenv("DVTM_CMD_FIFO", cmdpath, 1); break; #endif default: usage(); } } return init; } void keypress(int code) { Client *c; int len = 1; char buf[8] = { '\e' }; if (code == '\e') { /* pass characters following escape to the underlying app */ nodelay(stdscr, TRUE); while (len < sizeof(buf) - 1 && (buf[len] = getch()) != ERR) len++; buf[len] = '\0'; nodelay(stdscr, FALSE); } for (c = runinall ? clients : sel; c; c = c->next) { if (!c->minimized || isarrange(fullscreen)) { if (code == '\e') madtty_keypress_sequence(c->term, buf); else madtty_keypress(c->term, code); } if (!runinall) break; } } int main(int argc, char *argv[]) { if (!parse_args(argc, argv)) { setup(); startup(NULL); } while (running) { Client *c, *t; int r, nfds = 0; fd_set rd; if (need_screen_resize) { resize_screen(); need_screen_resize = false; } FD_ZERO(&rd); FD_SET(STDIN_FILENO, &rd); #ifdef CONFIG_CMDFIFO if (cmdfd != -1) { FD_SET(cmdfd, &rd); nfds = cmdfd; } #endif #ifdef CONFIG_STATUSBAR if (statusfd != -1) { FD_SET(statusfd, &rd); nfds = max(nfds, statusfd); } #endif for (c = clients; c; ) { if (c->died) { t = c->next; destroy(c); c = t; continue; } FD_SET(c->pty, &rd); nfds = max(nfds, c->pty); c = c->next; } r = select(nfds + 1, &rd, NULL, NULL, NULL); if (r == -1 && errno == EINTR) continue; if (r < 0) { perror("select()"); exit(EXIT_FAILURE); } if (FD_ISSET(STDIN_FILENO, &rd)) { int code = getch(); Key *key; if (code >= 0) { #ifdef CONFIG_MOUSE if (code == KEY_MOUSE) { handle_mouse(); } else #endif /* CONFIG_MOUSE */ if (is_modifier(code)) { int mod = code; code = getch(); if (code >= 0) { if (code == mod) keypress(code); else if ((key = keybinding(mod, code))) key->action.cmd(key->action.args); } } else if ((key = keybinding(0, code))) { key->action.cmd(key->action.args); } else { keypress(code); } } if (r == 1) /* no data available on pty's */ continue; } #ifdef CONFIG_CMDFIFO if (cmdfd != -1 && FD_ISSET(cmdfd, &rd)) handle_cmdfifo(); #endif #ifdef CONFIG_STATUSBAR if (statusfd != -1 && FD_ISSET(statusfd, &rd)) handle_statusbar(); #endif for (c = clients; c; ) { if (FD_ISSET(c->pty, &rd)) { if (madtty_process(c->term) < 0 && errno == EIO) { /* client probably terminated */ t = c->next; destroy(c); c = t; continue; } if (c != sel) { draw_content(c); if (!isarrange(fullscreen)) wnoutrefresh(c->window); } } c = c->next; } if (sel) { draw_content(sel); wnoutrefresh(sel->window); } doupdate(); } cleanup(); return 0; } dvtm-0.6/fullscreen.c0000644000175000017500000000016011453640555013272 0ustar marcmarcstatic void fullscreen(void) { Client *c; for(c = clients; c; c = c->next) resize(c, wax, way, waw, wah); } dvtm-0.6/madtty.h0000644000175000017500000000530411453640555012444 0ustar marcmarc/* LICENSE INFORMATION: This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation. Please refer to the COPYING file for more information. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Copyright © 2004 Bruno T. C. de Oliveira Copyright © 2006 Pierre Habouzit */ #ifndef MADTTY_MADTTY_H #define MADTTY_MADTTY_H #include #include #include #include #include #include #include enum { /* means escape sequence was handled */ MADTTY_HANDLER_OK, /* means the escape sequence was not recognized yet, but * there is hope that it still will once more characters * arrive (i.e. it is not yet complete). * * The library will thus continue collecting characters * and calling the handler as each character arrives until * either OK or NOWAY is returned. */ MADTTY_HANDLER_NOTYET, /* means the escape sequence was not recognized, and there * is no chance that it will even if more characters are * added to it. */ MADTTY_HANDLER_NOWAY }; typedef struct madtty_t madtty_t; typedef int (*madtty_handler_t)(madtty_t *, char *es); void madtty_init_colors(void); void madtty_init_vt100_graphics(void); void madtty_set_handler(madtty_t *, madtty_handler_t); void madtty_set_data(madtty_t *, void *); void *madtty_get_data(madtty_t *); madtty_t *madtty_create(int rows, int cols, int scroll_buf_sz); void madtty_resize(madtty_t *, int rows, int cols); void madtty_destroy(madtty_t *); pid_t madtty_forkpty(madtty_t *, const char *, const char *argv[], const char *envp[], int *pty); int madtty_getpty(madtty_t *); unsigned madtty_cursor(madtty_t *t); int madtty_process(madtty_t *); void madtty_keypress(madtty_t *, int keycode); void madtty_keypress_sequence(madtty_t *, const char *seq); void madtty_dirty(madtty_t *t); void madtty_draw(madtty_t *, WINDOW *win, int startrow, int startcol); void madtty_color_set(WINDOW *win, short fg, short bg); void madtty_scroll(madtty_t *, int rows); void madtty_noscroll(madtty_t *); void madtty_bell(madtty_t *, bool bell); void madtty_togglebell(madtty_t *); #endif /* MADTTY_MADTTY_H */ dvtm-0.6/tstack.c0000644000175000017500000000215111453640555012423 0ustar marcmarcvoid tstack(void) { unsigned int i, m, n, nx, ny, nw, nh, mh, tw; Client *c; for(n = 0, m = 0, c = clients; c; c = c->next, n++) if(c->minimized) m++; /* relative height */ mh = (wah - m) * (n == 1 || n - 1 == m ? 1 : mwfact); /* true if there are at least 2 non minimized clients */ if(n - 1 > m) tw = waw / (n - m - 1); nx = wax, nw = waw; for(i = 0, c = clients; c; c = c->next, i++){ if(i == 0){ /* master */ ny = way + wah - mh; nh = mh; } else { /* tile window */ if(i == 1){ nx = wax; ny = way + m; nh = wah - mh - ny + way; } if(i == n - m - 1){ /* last not minimized client */ nw = (wax + waw) - nx; } else if(i == n - m) { /* first minimized client */ nx = wax; --ny; nh = 1; nw = waw; } else if(c->minimized) { /* minimized window */ --ny; nh = 1; nw = waw; } else /* normal non minimized tile window */ nw = tw; if(i > 1 && !c->minimized){ mvvline(ny, nx, ACS_VLINE, nh); mvaddch(ny, nx, ACS_TTEE); ++nx, --nw; } } resize(c,nx,ny,nw,nh); if(n > 1 && i < n - m - 1) nx += nw; } } dvtm-0.6/statusbar.c0000644000175000017500000000317611453640555013152 0ustar marcmarcstatic int statusfd = -1; static char stext[512]; static int barpos = BARPOS; static unsigned int bh = 1, by; static void updatebarpos(void) { by = 0; wax = 0; way = 0; wah = height; if (statusfd == -1) return; if (barpos == BarTop) { wah -= bh; way += bh; } else if (barpos == BarBot) { wah -= bh; by = wah; } } static void drawbar() { wchar_t wbuf[sizeof stext]; int w, maxwidth = width - 2; if (barpos == BarOff || !*stext) return; curs_set(0); attrset(BAR_ATTR); madtty_color_set(stdscr, BAR_FG, BAR_BG); mvaddch(by, 0, '['); if (mbstowcs(wbuf, stext, sizeof stext) == -1) return; if ((w = wcswidth(wbuf, maxwidth)) == -1) return; if (BAR_ALIGN == ALIGN_RIGHT) { for (int i = 0; i + w < maxwidth; i++) addch(' '); } addstr(stext); if (BAR_ALIGN == ALIGN_LEFT) { for (; w < maxwidth; w++) addch(' '); } mvaddch(by, width - 1, ']'); attrset(NORMAL_ATTR); if (sel) curs_set(madtty_cursor(sel->term)); refresh(); } static void togglebar(const char *args[]) { if (barpos == BarOff) barpos = (BARPOS == BarOff) ? BarTop : BARPOS; else barpos = BarOff; updatebarpos(); arrange(); drawbar(); } static void handle_statusbar() { char *p; int r; switch (r = read(statusfd, stext, sizeof stext - 1)) { case -1: strncpy(stext, strerror(errno), sizeof stext - 1); stext[sizeof stext - 1] = '\0'; statusfd = -1; break; case 0: statusfd = -1; break; default: stext[r] = '\0'; p = stext + strlen(stext) - 1; for (; p >= stext && *p == '\n'; *p-- = '\0'); for (; p >= stext && *p != '\n'; --p); if (p > stext) strncpy(stext, p + 1, sizeof stext); drawbar(); } } dvtm-0.6/Makefile0000644000175000017500000000314011453640555012425 0ustar marcmarcinclude config.mk SRC += dvtm.c madtty.c OBJ = ${SRC:.c=.o} all: clean options dvtm options: @echo dvtm build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" .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 config.h config.mk \ ${SRC} tile.c bstack.c tstack.c grid.c fullscreen.c \ madtty.h statusbar.c mouse.c cmdfifo.c \ dvtm-status dvtm.1 dvtm-${VERSION} @tar -cf dvtm-${VERSION}.tar dvtm-${VERSION} @gzip dvtm-${VERSION}.tar @rm -rf dvtm-${VERSION} install: dvtm @echo stripping executable @strip -s dvtm @echo installing executable file to ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${PREFIX}/bin @cp -f dvtm ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm @cp -f dvtm-status ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm-status @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @sed "s/VERSION/${VERSION}/g" < dvtm.1 > ${DESTDIR}${MANPREFIX}/man1/dvtm.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dvtm.1 uninstall: @echo removing executable file from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/dvtm @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.6/tile.c0000644000175000017500000000215311453640555012071 0ustar marcmarcstatic void tile(void) { unsigned int i, m, n, nx, ny, nw, nh, nm, mw, th; Client *c; for(n = 0, m = 0, c = clients; c; c = c->next, n++) if(c->minimized) m++; nm = n - m; /* window geoms */ mw = (n == 1 || n - 1 == m) ? waw : mwfact * waw; /* check if there are at least 2 non minimized clients */ if(n - 1 > m) th = (wah - m) / (nm - 1); nx = wax; ny = way; for(i = 0, c = clients; c; c = c->next, i++) { if(i == 0) { /* master */ nw = mw; nh = (n - 1 > m) ? wah : wah - m; } else { /* tile window */ if(!c->minimized){ if(i == 1) { ny = way; nx += mw; nw = waw - mw; mvvline(ny, nx, ACS_VLINE, wah); mvaddch(ny, nx, ACS_TTEE); nx++, nw--; } /* remainder */ if(m == 0 && i + 1 == n) /* no minimized clients */ nh = (way + wah) - ny; else if(i == nm - 1) /* last not minimized client */ nh = (way + wah - (n - i - 1)) - ny; else nh = th; } else { nh = 1; ny = way + wah - (n - i); } if(i > 1 && nm > 1) mvaddch(ny, nx - 1, ACS_LTEE); } resize(c,nx,ny,nw,nh); if(n > 1 && th != wah) ny += nh; } } dvtm-0.6/dvtm.10000644000175000017500000000626211453640555012031 0ustar marcmarc.TH DVTM 1 dvtm\-VERSION .SH NAME dvtm \- dynamic virtual terminal manager .SH SYNOPSIS .B dvtm .RB [ \-v ] \ [ \-m \ mod ] \ [ \-s \ status-fifo ] \ [cmd...] .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 prints version information to standard output, then exits. .TP .B \-m mod set default modifier at runtime. .TP .B \-d escdelay 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 .B \-h nnn set the scrollback history buffer size at runtime. .TP .B \-s status-fifo if status-fifo is a named pipe it's content is read and displayed. See the dvtm-status script for an usage example. .TP .B [cmd...] Execute cmd after dvtm is started. .SH USAGE .SS Keyboard commands .TP .B Mod Each keybinding begins with Mod which defaults to ^g but can be changed in config.h or with the -m command line option. .TP .B Mod\-c Create a new shell window. .TP .B Mod\-x Close focused window. .TP .B Mod\-l Increases the master area width about 5% (all except grid and fullscreen layout). .TP .B Mod\-h Decreases the master area width about 5% (all except grid and fullscreen layout). .TP .B Mod\-j Focus next window. .TP .B Mod\-k Focus previous window. .TP .B Mod\-[1..n] Focus the .BR nth window. .TP .B Mod\-. Toggle minimization of current window. .TP .B Mod\-u Focus next non minimized window. .TP .B Mod\-i Focus prev non minimized window. .TP .B Mod\-m Maximize current window (change to fullscreen layout). .TP .B Mod\-PageUp Scroll up. .TP .B Mod\-PageDown Scroll down. .TP .B Mod\-Space Toggle between defined layouts (affects all windows). .TP .B Mod\-Enter Zooms/cycles current window to/from master area. .TP .B Mod\-t Change to vertical stack tiling layout. .TP .B Mod\-b Change to bottom stack tiling layout. .TP .B Mod\-g Change to grid layout. .TP .B Mod\-s Shows/hides the status bar. .TP .B Mod\-r Redraw whole screen. .TP .B Mod\-G Escape the next typed key. .TP .B Mod\-a Toggle keyboard multiplexing mode, if activated keypresses are sent to all non minimized windows. .TP .B Mod\-X Lock screen. .TP .B Mod\-B Toggle bell (off by default). .TP .B Mod\-M Toggle dvtm mouse grabbing. .TP .B Mod\-q Quit dvtm. .SS Mouse commands .TP .B Copy and Paste By default dvtm captures mouse events to provide the actions listed below. Unfortunately this interferes with the standard X copy and paste mechanism. To work around this you need to hold down SHIFT while selecting or pasting text. Alternatively you can disable mouse support at compile time, or use Mod\-M to toggle mouse support dynamically. .TP .B Button1 click Select window. .TP .B Button1 double click Select window and toggle maximization. .TP .B Button2 click Zooms/cycles current window to/from master area. .TP .B Button3 click Toggle minimization of current window. .SH EXAMPLE .TP See the dvtm-status script as an example. .SH CUSTOMIZATION dvtm is customized by creating a custom config.h and (re)compiling the source code. This keeps it fast, secure and simple. .SH AUTHOR dvtm is written by Marc Andre Tanner