pax_global_header00006660000000000000000000000064132574753340014527gustar00rootroot0000000000000052 comment=311a8c0c28296f8f87fb63349e0f3254c7481e14 .gitignore000066400000000000000000000000521325747533400130600ustar00rootroot00000000000000/config.h /dvtm /dvtm-editor *.css *.html .travis.yml000066400000000000000000000001341325747533400132020ustar00rootroot00000000000000language: c compiler: - gcc - clang env: - DEBUG= - DEBUG=debug script: make $DEBUG LICENSE000066400000000000000000000022231325747533400120770ustar00rootroot00000000000000MIT/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. Makefile000066400000000000000000000034201325747533400125320ustar00rootroot00000000000000include config.mk SRC = dvtm.c vt.c BIN = dvtm dvtm-status dvtm-editor dvtm-pager MANUALS = dvtm.1 dvtm-editor.1 dvtm-pager.1 VERSION = $(shell git describe --always --dirty 2>/dev/null || echo "0.15-git") CFLAGS += -DVERSION=\"${VERSION}\" DEBUG_CFLAGS = ${CFLAGS} -UNDEBUG -O0 -g -ggdb -Wall -Wextra -Wno-unused-parameter all: dvtm dvtm-editor config.h: cp config.def.h config.h dvtm: config.h config.mk *.c *.h ${CC} ${CFLAGS} ${SRC} ${LDFLAGS} ${LIBS} -o $@ dvtm-editor: dvtm-editor.c ${CC} ${CFLAGS} $^ ${LDFLAGS} -o $@ man: @for m in ${MANUALS}; do \ echo "Generating $$m"; \ sed -e "s/VERSION/${VERSION}/" "$$m" | mandoc -W warning -T utf8 -T xhtml -O man=%N.%S.html -O style=mandoc.css 1> "$$m.html" || true; \ done debug: clean @$(MAKE) CFLAGS='${DEBUG_CFLAGS}' clean: @echo cleaning @rm -f dvtm @rm -f dvtm-editor dist: clean @echo creating dist tarball @git archive --prefix=dvtm-${VERSION}/ -o dvtm-${VERSION}.tar.gz HEAD install: all @mkdir -p ${DESTDIR}${PREFIX}/bin @for b in ${BIN}; do \ echo "installing ${DESTDIR}${PREFIX}/bin/$$b"; \ cp -f "$$b" "${DESTDIR}${PREFIX}/bin" && \ chmod 755 "${DESTDIR}${PREFIX}/bin/$$b"; \ done @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @for m in ${MANUALS}; do \ sed -e "s/VERSION/${VERSION}/" < "$$m" > "${DESTDIR}${MANPREFIX}/man1/$$m" && \ chmod 644 "${DESTDIR}${MANPREFIX}/man1/$$m"; \ done @echo installing terminfo description @TERMINFO=${TERMINFO} tic -s dvtm.info uninstall: @for b in ${BIN}; do \ echo "removing ${DESTDIR}${PREFIX}/bin/$$b"; \ rm -f "${DESTDIR}${PREFIX}/bin/$$b"; \ done @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 @rm -f ${DESTDIR}${MANPREFIX}/man1/dvtm.1 .PHONY: all clean dist install uninstall debug README.md000066400000000000000000000321431325747533400123550ustar00rootroot00000000000000# 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.15](http://www.brain-dump.org/projects/dvtm/dvtm-0.15.tar.gz) [released](http://lists.suckless.org/dev/1601/28095.html) (09.01.2016) - [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.15.tar.gz](http://www.brain-dump.org/projects/dvtm/dvtm-0.15.tar.gz) with sha1sum 9bcec7af79b2adfead88c5513fa19b53dad6e134 dvtm-0.15.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. bstack.c000066400000000000000000000021771325747533400125150ustar00rootroot00000000000000static 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; } } } config.def.h000066400000000000000000000237061325747533400132560ustar00rootroot00000000000000/* 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 specifiy at most 3 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', }, { focusdown, { NULL } } }, { { MOD, 'K', }, { focusup, { NULL } } }, { { MOD, 'H', }, { focusleft, { NULL } } }, { { MOD, 'L', }, { focusright, { 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, { "dvtm-editor" } } }, { { MOD, 'E', }, { copymode, { "dvtm-pager" } } }, { { MOD, '/', }, { copymode, { "dvtm-pager", "/" } } }, { { 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 [cmd]: create a new window, run `cmd` in the shell if specified */ { "create", { create, { NULL } } }, /* focus : focus the window whose `DVTM_WINDOW_ID` is `win_id` */ { "focus", { focusid, { NULL } } }, /* tag [tag ...]: add +tag, remove -tag or set tag of the window with the given identifier */ { "tag", { tagid, { NULL } } }, }; /* gets executed when dvtm is started */ static Action actions[] = { { create, { NULL } }, }; static char const * const keytable[] = { /* add your custom key escape sequences */ }; config.mk000066400000000000000000000006301325747533400126700ustar00rootroot00000000000000# 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} -DNDEBUG ${CPPFLAGS} CC ?= cc dvtm-editor.1000066400000000000000000000032631325747533400134170ustar00rootroot00000000000000.Dd December 27, 2016 .Dt DVTM-EDITOR 1 .Os dvtm VERSION .Sh NAME .Nm dvtm-editor .Nd make a text editor act as a filter . . .Sh SYNOPSIS . .Nm ARGS... . . .Sh DESCRIPTION . The .Nm is a file buffering utility used by the .Xr dvtm 1 terminal multiplexer to implement its copy mode. It reads the standard input and saves it to a temporary file, then opens an editor according to the .Sx "ENVIRONMENT VARIABLES" . .Pp If the invoked editor terminates with a non-zero exit status or the file modification time remains unchanged, .Nm does not output anything. Otherwise, it outputs the content of the modified temporary file to stdout. .Pp All command line arguments are forwarded verbatim. .Xr dvtm 1 uses this to adjust the initial view port by passing .Sy +n , meaning the start of line .Sy n should be displayed. . . .Sh ENVIRONMENT VARIABLES . .Nm will try to find the user editor by checking these variables in order: . .Bl -tag -width indent .It Ev DVTM_EDITOR Permitting to invoke an editor specific to dvtm, or set particular flags. . .It Ev VISUAL , Ev EDITOR Falling back to global defaults: .Ev VISUAL and .Ev EDITOR . .El .Pp If no editor is found, .Xr vi 1 is used. . . .Sh FILES . The temporary files are created according to the template: .Pa /tmp/dvtm-editor.XXXXXX . .Pp .Pa /dev/tty is opened to obtain a controlling tty which is used for the standard input/output streams of the invoked editor. . .Sh NOTES . Using .Xr vis 1 as editor is particularly convenient because .Ic :wq! in visual mode will reduce the file to the currently active selection(s). . .Sh SEE ALSO . .Xr vi 1 , .Xr dvtm 1 , .Xr dvtm-pager 1 . . .Sh AUTHOR . dvtm is written by .An Marc André Tanner Aq Mt mat at brain-dump.org dvtm-editor.c000066400000000000000000000106051325747533400134770ustar00rootroot00000000000000/* Invoke $EDITOR as a filter. * * Copyright (c) 2016 Dmitry Bogatov * Copyright (c) 2017 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 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 static void error(const char *msg, ...) { va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); if (errno) fprintf(stderr, ": %s", strerror(errno)); fprintf(stderr, "\n"); } int main(int argc, char *argv[]) { int exit_status = EXIT_FAILURE, tmp_write = -1; const char *editor = getenv("DVTM_EDITOR"); if (!editor) editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); if (!editor) editor = "vi"; char tempname[] = "/tmp/dvtm-editor.XXXXXX"; if ((tmp_write = mkstemp(tempname)) == -1) { error("failed to open temporary file `%s'", tempname); goto err; } /* POSIX does not mandates modes of temporary file. */ if (fchmod(tmp_write, 0600) == -1) { error("failed to change mode of temporary file `%s'", tempname); goto err; } char buffer[2048]; ssize_t bytes; while ((bytes = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) { do { ssize_t written = write(tmp_write, buffer, bytes); if (written == -1) { error("failed to write data to temporary file `%s'", tempname); goto err; } bytes -= written; } while (bytes > 0); } if (fsync(tmp_write) == -1) { error("failed to fsync temporary file `%s'", tempname); goto err; } struct stat stat_before; if (fstat(tmp_write, &stat_before) == -1) { error("failed to stat newly created temporary file `%s'", tempname); goto err; } if (close(tmp_write) == -1) { error("failed to close temporary file `%s'", tempname); goto err; } pid_t pid = fork(); if (pid == -1) { error("failed to fork editor process"); goto err; } else if (pid == 0) { int tty = open("/dev/tty", O_RDWR); if (tty == -1) { error("failed to open /dev/tty"); _exit(1); } if (dup2(tty, STDIN_FILENO) == -1) { error("failed to set tty as stdin"); _exit(1); } if (dup2(tty, STDOUT_FILENO) == -1) { error("failed to set tty as stdout"); _exit(1); } if (dup2(tty, STDERR_FILENO) == -1) { error("failed to set tty as stderr"); _exit(1); } close(tty); const char *editor_argv[argc+2]; editor_argv[0] = editor; for (int i = 1; i < argc; i++) editor_argv[i] = argv[i]; editor_argv[argc] = tempname; editor_argv[argc+1] = NULL; execvp(editor, (char* const*)editor_argv); error("failed to exec editor process `%s'", editor); _exit(127); } int status; if (waitpid(pid, &status, 0) == -1) { error("waitpid failed"); goto err; } if (!WIFEXITED(status)) { error("editor invocation failed"); goto err; } if ((status = WEXITSTATUS(status)) != 0) { error("editor terminated with exit status: %d", status); goto err; } int tmp_read = open(tempname, O_RDONLY); if (tmp_read == -1) { error("failed to open for reading of edited temporary file `%s'", tempname); goto err; } struct stat stat_after; if (fstat(tmp_read, &stat_after) == -1) { error("failed to stat edited temporary file `%s'", tempname); goto err; } if (stat_before.st_mtime == stat_after.st_mtime) goto ok; /* no modifications */ while ((bytes = read(tmp_read, buffer, sizeof(buffer))) > 0) { do { ssize_t written = write(STDOUT_FILENO, buffer, bytes); if (written == -1) { error("failed to write data to stdout"); goto err; } bytes -= written; } while (bytes > 0); } ok: exit_status = EXIT_SUCCESS; err: if (tmp_write != -1) unlink(tempname); return exit_status; } dvtm-pager000077500000000000000000000002301325747533400130620ustar00rootroot00000000000000#!/bin/sh if [ ! -z "$DVTM_PAGER" ]; then PAGER="$DVTM_PAGER" elif [ ! -z "$PAGER" ]; then PAGER="$PAGER" else PAGER="less -R" fi exec $PAGER "$@" dvtm-pager.1000066400000000000000000000021461325747533400132260ustar00rootroot00000000000000.Dd January 03, 2017 .Dt DVTM-PAGER 1 .Os dvtm VERSION .Sh NAME .Nm dvtm-pager .Nd select apropriate pager for dvtm . . .Sh SYNOPSIS . .Nm ARGS... . . .Sh DESCRIPTION . .Nm is an utility used by the .Xr dvtm 1 terminal multiplexer to display its scrollback history using a suitable pager. .Pp The invoked pager is expected to display the data sent to its standard input. The data stream might contain ANSI color escape sequence. All command line arguments are forwarded verbatim. .Xr dvtm 1 uses this to adjust the initial view port by passing .Sy +n , meaning the start of line .Sy n should be displayed. . . .Sh ENVIRONMENT VARIABLES . .Nm will try to find the preferred pager by checking these variables in order: . .Bl -tag -width indent .It Ev DVTM_PAGER Permitting to invoke a pager specific to dvtm, or set particular flags such as .Fl R for .Xr less 1 . . .It Ev PAGER Falling back to the default pager. .El .Pp If none of these variables are set, .Xr less 1 is used. . . .Sh SEE ALSO . .Xr less 1 , .Xr dvtm 1 , .Xr dvtm-editor 1 . . .Sh AUTHOR . dvtm is written by .An Marc André Tanner Aq Mt mat at brain-dump.org dvtm-status000077500000000000000000000003771325747533400133230ustar00rootroot00000000000000#!/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.1000066400000000000000000000142151325747533400121320ustar00rootroot00000000000000.Dd December 27, 2016 .Dt DVTM 1 .Os dvtm VERSION .Sh NAME .Nm dvtm .Nd dynamic virtual terminal manager . . .Sh SYNOPSIS . .Nm .Op Fl v .Op Fl M .Op Fl m Ar modifier .Op Fl d Ar delay .Op Fl h Ar lines .Op Fl t Ar title .Op Fl s Ar status-fifo .Op Fl c Ar cmd-fifo .Op Ar command Ar ... . . .Sh DESCRIPTION . .Nm is a dynamic tiling window manager for the console. .Pp As a console window manager it tries to make it easy to work with multiple console based applications. . .Bl -tag -width 8 .It Fl v Print version information to standard output and exit. . .It Fl M Toggle default mouse grabbing upon startup. Use this to allow normal mouse operation under X. . .It Fl m Ar modifier Set command modifier at runtime. . .It Fl d Ar 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. . .It Fl h Ar lines Set the scrollback history buffer size at runtime. . .It Fl t Ar title Set a static terminal .Ar title and don't change it to the one of the currently focused window. . .It Fl s Ar status-fifo Open or create the named pipe .Pa status-fifo read its content and display it in the statusbar. See the .Xr dvtm-status 1 script for an usage example. . .It Fl c Ar cmd-fifo Open or create the named pipe .Pa cmd-fifo and look for commands to execute which were defined in .Pa config.h . . .It Ar command Ar ... Execute .Ar command (s), each in a separate window. .El . . .Sh USAGE . .Ss Keyboard commands . Each keybinding begins with .Ic Mod which defaults to .Ic ^g but can be changed in .Pa config.h or with the .Fl m command line option. . .Bl -tag -width 8 .It Ic Mod-c Create a new shell window. . .It Ic Mod-C Create a new shell window using the current working directory of the focused window. . .It Ic Mod-x-x Close focused window. . .It Ic Mod-l Increases the master area width about 5% (all except grid and fullscreen layout). . .It Ic Mod-h Decreases the master area width about 5% (all except grid and fullscreen layout). . .It Ic Mod-i Increase number of windows displayed in the master area. . .It Ic Mod-d Decrease number of windows displayed in the master area. . .It Ic Mod-j Focus next window. . .It Ic Mod-k Focus previous window. . .It Ic Mod-J Focus window below. . .It Ic Mod-K Focus window above. . .It Ic Mod-H Focus window to the left. . .It Ic Mod-L Focus window to the right. . .It Ic Mod-[0..9] Focus the [0..9]-th window. . .It Ic Mod-Tab Focus previously selected window. . .It Ic Mod-. Toggle minimization of current window. . .It Ic Mod-m Maximize current window (change to fullscreen layout). . .It Ic Shift-PageUp .It Ic Mod-PageUp Scroll up. . .It Ic Shift-PageDown .It Ic Mod-PageDown Scroll down. . .It Ic Mod-Space Toggle between defined layouts (affects all windows). . .It Ic Mod-Enter Zooms/cycles current window to/from master area. . .It Ic Mod-f Change to vertical stack tiling layout. . .It Ic Mod-b Change to bottom stack tiling layout. . .It Ic Mod-g Change to grid layout. . .It Ic Mod-s Show/hide the status bar. . .It Ic Mod-S Toggle position of the status bar between top and bottom. . .It Ic Mod-r . .It Ic Mod-^L Redraw whole screen. . .It Ic Mod-a Toggle keyboard multiplexing mode, if activated keypresses are sent to all visible windows. . .It Ic Mod-M Toggle dvtm mouse grabbing. . .It Ic Mod-e Enter copy mode (see section below for further information). . .It Ic Mod-/ Enter copy mode and start searching forward (assumes a vi-like editor). . .It Ic Mod-p Paste last copied text from copy mode at current cursor position. . .It Ic Mod-? Show this manual page. . .It Ic Mod-Mod Send the Mod key. . .It Ic Mod-F[1..n] .It Ic Mod-v-[1..n] View all windows with n-th tag. . .It Ic Mod-0 View all windows with any tag. . .It Ic Mod-v-Tab Toggles to the previously selected tags. . .It Ic Mod-V-[1..n] Add/remove all windows with nth tag to/from the view. . .It Ic Mod-t-[1..n] Apply nth tag to focused window. . .It Ic Mod-T-[1..n] Add/remove nth tag to/from focused window. . .It Ic Mod-q-q Quit dvtm. .El . . .Ss Mouse commands . 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 .Ic Shift while selecting or pasting text. Alternatively you can disable mouse support at compile time, start dvtm with the .Fl M flag or toggle mouse support during runtime with .Ic Mod-M . . .Bl -tag -width 8 .It Ic Button1 click Focus window. . .It Ic Button1 double click Focus window and toggle maximization. . .It Ic Button2 click Zoom/cycle current window to/from master area. . .It Ic Button3 click Toggle minimization of current window. .El . . .Ss Copy mode . Copy mode gives easy access to past output by piping it to .Xr dvtm-editor 1 , opening an editor. What the editor writes will be stored in an internal register and can be pasted into other clients (via .Ic Mod-p ). . . .Sh ENVIRONMENT VARIABLES . .Bl -tag -width 8 .It Ev DVTM Each process spawned by dvtm will have this variable set to the dvtm version it is running under. . .It Ev DVTM_WINDOW_ID Each process also has access to its constant and unique window id. . .It Ev 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. . .It Ev DVTM_TERM By default dvtm uses its own terminfo file and therefore sets .Ev TERM=dvtm within the client windows. This can be overridden by setting the .Ev DVTM_TERM environment variable to a valid terminal name before launching dvtm. . .It Ev DVTM_EDITOR When entering the copymode dvtm pipes the whole scroll back buffer to .Xr dvtm-editor 1 which opens the content in .Ev DVTM_EDITOR , with fallbacks to .Ev VISUAL , .Ev EDITOR and .Xr vi 1 .Pa config.h is used instead. .El . . .Sh EXAMPLE . See the .Xr dvtm-status 1 script as an example of how to display text in the status bar. . . .Sh FILES . .Nm is customized by creating a custom .Pa config.h and (re)compiling the source code. This keeps it fast, secure and simple. . . .Sh SEE ALSO . .Xr abduco 1 , .Xr dvtm-status 1 . . .Sh AUTHOR . dvtm is written .An Marc André Tanner Aq Mt mat at brain-dump.org dvtm.c000066400000000000000000001154061325747533400122200ustar00rootroot00000000000000/* * 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 #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 8 typedef struct { void (*cmd)(const char *args[]); const char *args[3]; } 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 focusid(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 focusup(const char *args[]); static void focusdown(const char *args[]); static void focusleft(const char *args[]); static void focusright(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 tagid(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(runinall ? TAG_SEL : TAG_NORMAL); addstr(layout->symbol); attrset(TAG_NORMAL); 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 && c->urgent) attrs = URGENT_ATTR; if (sel == c || (runinall && !c->minimized)) attrs = SELECTED_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) 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); fflush(stdout); } } 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 >= way+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) { 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; if (!tag) return ~0; for (i = 0; (i < LENGTH(tags)) && strcmp(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 tagid(const char *args[]) { if (!args[0] || !args[1]) return; const int win_id = atoi(args[0]); for (Client *c = clients; c; c = c->next) { if (c->id == win_id) { unsigned int ntags = c->tags; for (unsigned int i = 1; i < MAX_ARGS && args[i]; i++) { if (args[i][0] == '+') ntags |= bitoftag(args[i]+1); else if (args[i][0] == '-') ntags &= ~bitoftag(args[i]+1); else ntags = bitoftag(args[i]); } ntags &= TAGMASK; if (ntags) { c->tags = ntags; tagschanged(); } return; } } } 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) { int key = -1; 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++) { if (t > 255) { key = t; break; } 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 (key != -1) vt_keypress(c->term, key); } 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; memset(&sa, 0, sizeof 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; } if (args && args[0]) { c->cmd = args[0]; char name[PATH_MAX]; strncpy(name, args[0], sizeof(name)); name[sizeof(name)-1] = '\0'; strncpy(c->title, basename(name), sizeof(c->title)); } else { c->cmd = shell; } if (args && args[1]) strncpy(c->title, args[1], sizeof(c->title)); 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); applycolorrules(c); 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 (!args || !args[0] || !sel || sel->editor) return; bool colored = strstr(args[0], "pager") != NULL; if (!(sel->editor = vt_create(sel->h - sel->has_title_line, sel->w, 0))) return; int *to = &sel->editor_fds[0]; int *from = strstr(args[0], "editor") ? &sel->editor_fds[1] : NULL; sel->editor_fds[0] = sel->editor_fds[1] = -1; const char *argv[3] = { args[0], NULL, NULL }; char argline[32]; int line = vt_content_start(sel->app); snprintf(argline, sizeof(argline), "+%d", line); argv[1] = argline; if (vt_forkpty(sel->editor, args[0], 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[1]) vt_write(sel->editor, args[1], strlen(args[1])); } 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 focusid(const char *args[]) { if (!args[0]) return; const int win_id = atoi(args[0]); for (Client *c = clients; c; c = c->next) { if (c->id == win_id) { focus(c); if (c->minimized) toggleminimize(NULL); if (!isvisible(c)) { c->tags |= tagset[seltags]; tagschanged(); } 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 focusup(const char *args[]) { if (!sel) return; /* avoid vertical separator, hence +1 in x direction */ Client *c = get_client_by_coord(sel->x + 1, sel->y - 1); if (c) focus(c); else focusprev(args); } static void focusdown(const char *args[]) { if (!sel) return; Client *c = get_client_by_coord(sel->x, sel->y + sel->h); if (c) focus(c); else focusnext(args); } static void focusleft(const char *args[]) { if (!sel) return; Client *c = get_client_by_coord(sel->x - 2, sel->y); if (c) focus(c); else focusprev(args); } static void focusright(const char *args[]) { if (!sel) return; Client *c = get_client_by_coord(sel->x + sel->w + 1, sel->y); if (c) focus(c); else focusnext(args); } 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; drawbar(); 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); setlayout(isarrange(fullscreen) ? NULL : 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; r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1); if (r <= 0) { cmdfifo.fd = -1; return; } 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 < 0) { if (errno == EINTR) continue; 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, key_index))) { unsigned int key_length = MAX_KEYS; while (key_length > 1 && !binding->keys[key_length-1]) 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.info000066400000000000000000000042301325747533400127210ustar00rootroot00000000000000dvtm|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, fibonacci.c000066400000000000000000000037111325747533400131560ustar00rootroot00000000000000static 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); } forkpty-aix.c000066400000000000000000000043261325747533400135210ustar00rootroot00000000000000/* * 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; } forkpty-sunos.c000066400000000000000000000040751325747533400141100ustar00rootroot00000000000000/* * 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; } fullscreen.c000066400000000000000000000002071325747533400134000ustar00rootroot00000000000000static void fullscreen(void) { for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) resize(c, wax, way, waw, wah); } grid.c000066400000000000000000000032271325747533400121700ustar00rootroot00000000000000static 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++; } } testsuite.sh000077500000000000000000000023131325747533400134620ustar00rootroot00000000000000#!/bin/sh MOD="" # CTRL+g ESC="" # \e DVTM="./dvtm" export 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" 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 sh_cmd "cat <<'EOF' > $COPY" dvtm_cmd 'p' sh_cmd 'EOF' 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 tile.c000066400000000000000000000020141325747533400121710ustar00rootroot00000000000000static 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; } } } tstack.c000066400000000000000000000016641325747533400125370ustar00rootroot00000000000000static 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++; } } vstack.c000066400000000000000000000012541325747533400125340ustar00rootroot00000000000000/* 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++; } } vt.c000066400000000000000000001336301325747533400116760ustar00rootroot00000000000000/* * 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 MIN(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(MAX_COLOR_PAIRS, SHRT_MAX); if (COLORS) color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); /* * XXX: On undefined color-pairs NetBSD curses pair_content() set fg * and bg to default colors while ncurses set them respectively to * 0 and 0. Initialize all color-pairs in order to have consistent * behaviour despite the implementation used. */ for (short i = 1; i < color_pairs_max; i++) init_pair(i, 0, 0); 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; } vt.h000066400000000000000000000043571325747533400117060ustar00rootroot00000000000000/* * 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 */