├── .gitignore ├── Makefile ├── Makefile.darwin ├── README.rst ├── config.def.h ├── mtm.1 ├── mtm.c ├── mtm.ti ├── pair.c ├── screenshot.png ├── screenshot2.png ├── vtparser.c ├── vtparser.h ├── vttest1.png └── vttest2.png /.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | 3 | *~ 4 | *.swp 5 | 6 | *.o 7 | mtm 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS ?= -std=c99 -Wall -Wextra -pedantic -Os 3 | FEATURES ?= -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED 4 | HEADERS ?= 5 | LIBPATH ?= 6 | DESTDIR ?= /usr/local 7 | MANDIR ?= $(DESTDIR)/share/man/man1 8 | CURSESLIB ?= ncursesw 9 | LIBS ?= -l$(CURSESLIB) -lutil 10 | 11 | all: mtm 12 | 13 | mtm: vtparser.c mtm.c pair.c config.h 14 | $(CC) $(CFLAGS) $(FEATURES) -o $@ $(HEADERS) vtparser.c mtm.c pair.c $(LIBPATH) $(LIBS) 15 | strip mtm 16 | 17 | config.h: config.def.h 18 | cp -i config.def.h config.h 19 | 20 | install: mtm 21 | mkdir -p $(DESTDIR)/bin $(MANDIR) 22 | cp mtm $(DESTDIR)/bin 23 | cp mtm.1 $(MANDIR) 24 | 25 | uninstall: 26 | rm -f $(DESTDIR)/bin/mtm 27 | rm -f $(MANDIR)/mtm.1 28 | 29 | install-terminfo: mtm.ti 30 | tic -s -x mtm.ti 31 | 32 | clean: 33 | rm -f *.o mtm 34 | -------------------------------------------------------------------------------- /Makefile.darwin: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS ?= -std=c99 -Wall -Wextra -pedantic -Os 3 | FEATURES ?= -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED -DWCHAR_IS_UNICODE 4 | HEADERS ?= 5 | LIBPATH ?= 6 | DESTDIR ?= /usr/local 7 | MANDIR ?= $(DESTDIR)/man/man1 8 | CURSESLIB ?= curses 9 | LIBS ?= -l$(CURSESLIB) -lutil 10 | 11 | all: mtm 12 | 13 | mtm: vtparser.c mtm.c pair.c config.h 14 | $(CC) $(CFLAGS) $(FEATURES) -o $@ $(HEADERS) vtparser.c mtm.c pair.c $(LIBPATH) $(LIBS) 15 | strip mtm 16 | 17 | config.h: config.def.h 18 | cp -i config.def.h config.h 19 | 20 | install: mtm 21 | mkdir -p $(DESTDIR)/bin $(MANDIR) 22 | cp mtm $(DESTDIR)/bin 23 | cp mtm.1 $(MANDIR) 24 | 25 | install-terminfo: mtm.ti 26 | tic -s -x mtm.ti 27 | 28 | clean: 29 | rm -f *.o mtm 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | mtm is the Micro Terminal Multiplexer, a terminal multiplexer. 5 | 6 | It has four major features/principles: 7 | 8 | Simplicity 9 | There are only a few commands, two of which are hardly ever used. 10 | There are no modes, no dozens of commands, no crazy feature list. 11 | 12 | Compatibility 13 | mtm emulates a classic ANSI text terminal. That means it should 14 | work out of the box on essentially all terminfo/termcap-based systems 15 | (even pretty old ones), without needing to install a new termcap entry. 16 | 17 | Size 18 | mtm is small. 19 | The entire project is around 1000 lines of code. 20 | 21 | Stability 22 | mtm is "finished" as it is now. You don't need to worry about it 23 | changing on you unexpectedly. The only changes that can happen at 24 | this point are: 25 | 26 | - Bug fixes. 27 | - Translation improvements. 28 | - Accessibility improvements. 29 | - Fixes to keep it working on modern OSes. 30 | 31 | Community 32 | ========= 33 | 34 | Rob posts updates about mtm on Twitter at http://twitter.com/TheKingAdRob. 35 | 36 | Installation 37 | ============ 38 | Installation and configuration is fairly simple: 39 | 40 | - You need ncursesw. 41 | If you want to support terminal resizing, ncursesw needs to be 42 | compiled with its internal SIGWINCH handler; this is true for most 43 | precompiled distributions. Other curses implementations might work, 44 | but have not been tested. 45 | - Edit the variables at the top of the Makefile if you need to 46 | (you probably don't). 47 | - If you want to change the default keybindings or other compile-time flags, 48 | copy `config.def.h` to `config.h` and edit the copy. Otherwise the build 49 | process will use the defaults. 50 | - Run:: 51 | 52 | make 53 | 54 | or:: 55 | 56 | make CURSESLIB=curses 57 | 58 | or:: 59 | 60 | make HEADERS='-DNCURSESW_INCLUDE_H=""' 61 | 62 | whichever works for you. 63 | - Run `make install` if desired. 64 | 65 | Usage 66 | ===== 67 | 68 | Usage is simple:: 69 | 70 | mtm [-T NAME] [-t NAME] [-c KEY] 71 | 72 | The `-T` flag tells mtm to assume a different kind of host terminal. 73 | 74 | The `-t` flag tells mtm what terminal type to advertise itself as. 75 | Note that this doesn't change how mtm interprets control sequences; it 76 | simply controls what the `TERM` environment variable is set to. 77 | 78 | The `-c` flag lets you specify a keyboard character to use as the "command 79 | prefix" for mtm when modified with *control* (see below). By default, 80 | this is `g`. 81 | 82 | Once inside mtm, things pretty much work like any other terminal. However, 83 | mtm lets you split up the terminal into multiple virtual terminals. 84 | 85 | At any given moment, exactly one virtual terminal is *focused*. It is 86 | to this terminal that keyboad input is sent. The focused terminal is 87 | indicated by the location of the cursor. 88 | 89 | The following commands are recognized in mtm, when preceded by the command 90 | prefix (by default *ctrl-g*): 91 | 92 | Up/Down/Left/Right Arrow 93 | Focus the virtual terminal above/below/to the left of/to the right of 94 | the currently focused terminal. 95 | 96 | o 97 | Focus the previously-focused virtual terminal. 98 | 99 | h / v 100 | Split the focused virtual terminal in half horizontally/vertically, 101 | creating a new virtual terminal to the right/below. The new virtual 102 | terminal is focused. 103 | 104 | w 105 | Delete the focused virtual terminal. Some other nearby virtual 106 | terminal will become focused if there are any left. mtm will exit 107 | once all virtual terminals are closed. Virtual terminals will also 108 | close if the program started inside them exits. 109 | 110 | l 111 | Redraw the screen. 112 | 113 | PgUp/PgDown/End 114 | Scroll the screen back/forward half a screenful, or recenter the 115 | screen on the actual terminal. 116 | 117 | That's it. There aren't dozens of commands, there are no modes, there's 118 | nothing else to learn. 119 | 120 | (Note that these keybindings can be changed at compile time.) 121 | 122 | Screenshots 123 | ----------- 124 | mtm running three instances of `tine `_ 125 | 126 | .. image:: screenshot2.png 127 | 128 | mtm running various other programs 129 | 130 | .. image:: screenshot.png 131 | 132 | mtm showing its compatibility 133 | 134 | .. image:: vttest1.png 135 | .. image:: vttest2.png 136 | 137 | Compatibility 138 | ============= 139 | (Note that you only need to read this section if you're curious. mtm should 140 | just work out-of-the-box for you, thanks to the efforts of the various 141 | hackers over the years to make terminal-independence a reality.) 142 | 143 | By default, mtm advertises itself as a `screen-bce` terminal. This is what `GNU 144 | screen` and `tmux` advertise themselves as, and is a well-known terminal 145 | type that has been in the default terminfo database for decades. 146 | 147 | (Note that this should not be taken to imply that anyone involved in the 148 | `GNU screen` or `tmux` projects endorses or otherwise has anything to do 149 | with mtm, and vice-versa. Their work is excellent, though, and you should 150 | definitely check it out.) 151 | 152 | The (optional!) `mtm` Terminal Types 153 | ------------------------ 154 | mtm comes with a terminfo description file called mtm.ti. This file 155 | describes all of the features supported by mtm. 156 | 157 | If you want to install this terminal type, use the `tic` compiler that 158 | comes with ncurses:: 159 | 160 | tic -s -x mtm.ti 161 | 162 | or simply:: 163 | 164 | make install-terminfo 165 | 166 | This will install the following terminal types: 167 | 168 | mtm 169 | This terminal type supports all of the features of mtm, but with 170 | the default 8 "ANSI" colors only. 171 | 172 | mtm-256color 173 | Note that mtm is not magic and cannot actually display more colors 174 | than the host terminal supports. 175 | 176 | mtm-noutf 177 | This terminal type supports everything the mtm terminal type does, 178 | but does not advertise UTF8 capability. 179 | 180 | That command will compile and install the terminfo entry. After doing so, 181 | calling mtm with `-t mtm`:: 182 | 183 | mtm -t mtm 184 | 185 | will instruct programs to use that terminfo entry. 186 | You can, of course, replace `mtm` with any of the other above terminal 187 | types. 188 | 189 | Using these terminfo entries allows programs to use the full power of mtm's 190 | terminal emulation, but it is entirely optional. A primary design goal 191 | of mtm was for it to be completely usable on systems that didn't have the 192 | mtm terminfo entry installed. By default, mtm advertises itself as the 193 | widely-available `screen-bce` terminal type. 194 | 195 | Copyright and License 196 | ===================== 197 | 198 | Copyright 2016-2019 Rob King 199 | 200 | This program is free software: you can redistribute it and/or modify 201 | it under the terms of the GNU General Public License as published by 202 | the Free Software Foundation, either version 3 of the License, or 203 | (at your option) any later version. 204 | 205 | This program is distributed in the hope that it will be useful, 206 | but WITHOUT ANY WARRANTY; without even the implied warranty of 207 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 208 | GNU General Public License for more details. 209 | 210 | You should have received a copy of the GNU General Public License 211 | along with this program. If not, see . 212 | 213 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* Old versions of ncurses don't support A_ITALIC. 2 | * Define this to disable it if the situation isn't automatically detected. 3 | #define NO_ITALICS 4 | */ 5 | 6 | /* mtm advertises support for italic text by default. If the host 7 | * terminal does not support italics, this will fail. Define this 8 | * to map italic text to reverse text. 9 | #define REVERSE_ITALICS 10 | */ 11 | 12 | /* mtm by default will advertise itself as a "screen-bce" terminal. 13 | * This is the terminal type advertised by such programs as 14 | * screen(1) and tmux(1) and is a widely-supported terminal type. 15 | * mtm supports emulating the "screen-bce" terminal very well, and this 16 | * is a reasonable default. 17 | * 18 | * However, you can change the default terminal that mtm will 19 | * advertise itself as. There's the "mtm" terminal type that is 20 | * recommended for use if you know it will be available in all the 21 | * environments in which mtm will be used. It advertises a few 22 | * features that mtm has that the default "screen-bce" terminfo doesn't 23 | * list, meaning that terminfo-aware programs may get a small 24 | * speed boost. 25 | */ 26 | #define DEFAULT_TERMINAL "screen-bce" 27 | #define DEFAULT_256_COLOR_TERMINAL "screen-256color-bce" 28 | 29 | /* Sets how long (in milliseconds) mtm should wait for an escape sequence 30 | * to follow after the escape key is pressed before sending it on to 31 | * the focused virtual terminal. 32 | */ 33 | #define ESCAPE_TIME 500 34 | 35 | /* mtm supports a scrollback buffer, allowing users to scroll back 36 | * through the output history of a virtual terminal. The SCROLLBACK 37 | * knob controls how many lines are saved (minus however many are 38 | * currently displayed). 1000 seems like a good number. 39 | * 40 | * Note that every virtual terminal is sized to be at least this big, 41 | * so setting a huge number here might waste memory. It is recommended 42 | * that this number be at least as large as the largest terminal you 43 | * expect to use is tall. 44 | */ 45 | #define SCROLLBACK 1000 46 | 47 | /* The default command prefix key, when modified by cntrl. 48 | * This can be changed at runtime using the '-c' flag. 49 | */ 50 | #define COMMAND_KEY 'g' 51 | 52 | /* The change focus keys. */ 53 | #define MOVE_UP CODE(KEY_UP) 54 | #define MOVE_DOWN CODE(KEY_DOWN) 55 | #define MOVE_RIGHT CODE(KEY_RIGHT) 56 | #define MOVE_LEFT CODE(KEY_LEFT) 57 | #define MOVE_OTHER KEY(L'o') 58 | 59 | /* The split terminal keys. */ 60 | #define HSPLIT KEY(L'h') 61 | #define VSPLIT KEY(L'v') 62 | 63 | /* The delete terminal key. */ 64 | #define DELETE_NODE KEY(L'w') 65 | 66 | /* does nothing, specifically */ 67 | #define BAILOUT KEY(L'c') 68 | 69 | /* clears the scrollback and everything */ 70 | #define NUKE KEY(L'k') 71 | 72 | /* The force redraw key. */ 73 | #define REDRAW KEY(L'l') 74 | 75 | /* The scrollback keys. */ 76 | #define SCROLLUP CODE(KEY_PPAGE) 77 | #define SCROLLDOWN CODE(KEY_NPAGE) 78 | #define RECENTER CODE(KEY_END) 79 | 80 | /* The path for the wide-character curses library. */ 81 | #ifndef NCURSESW_INCLUDE_H 82 | #if defined(__APPLE__) || !defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) 83 | #define NCURSESW_INCLUDE_H 84 | #else 85 | #define NCURSESW_INCLUDE_H 86 | #endif 87 | #endif 88 | #include NCURSESW_INCLUDE_H 89 | 90 | /* Includes needed to make forkpty(3) work. */ 91 | #ifndef FORKPTY_INCLUDE_H 92 | #if defined(__APPLE__) || defined(__OpenBSD__) 93 | #define FORKPTY_INCLUDE_H 94 | #elif defined(__FreeBSD__) 95 | #define FORKPTY_INCLUDE_H 96 | #else 97 | #define FORKPTY_INCLUDE_H 98 | #endif 99 | #endif 100 | #include FORKPTY_INCLUDE_H 101 | 102 | /* You probably don't need to alter these much, but if you do, 103 | * here is where you can define alternate character sets. 104 | * 105 | * Note that if your system's wide-character implementation 106 | * maps directly to Unicode, the preferred Unicode characters 107 | * will be used automatically if your system declares such 108 | * support. If it doesn't declare it, define WCHAR_IS_UNICODE to 109 | * force Unicode to be used. 110 | */ 111 | #define MAXMAP 0x7f 112 | static wchar_t CSET_US[MAXMAP]; /* "USASCII"...really just the null table */ 113 | 114 | #if defined(__STDC_ISO_10646__) || defined(WCHAR_IS_UNICODE) 115 | static wchar_t CSET_UK[MAXMAP] ={ /* "United Kingdom"...really just Pound Sterling */ 116 | [L'#'] = 0x00a3 117 | }; 118 | 119 | static wchar_t CSET_GRAPH[MAXMAP] ={ /* Graphics Set One */ 120 | [L'-'] = 0x2191, 121 | [L'}'] = 0x00a3, 122 | [L'~'] = 0x00b7, 123 | [L'{'] = 0x03c0, 124 | [L','] = 0x2190, 125 | [L'+'] = 0x2192, 126 | [L'.'] = 0x2193, 127 | [L'|'] = 0x2260, 128 | [L'>'] = 0x2265, 129 | [L'`'] = 0x25c6, 130 | [L'a'] = 0x2592, 131 | [L'b'] = 0x2409, 132 | [L'c'] = 0x240c, 133 | [L'd'] = 0x240d, 134 | [L'e'] = 0x240a, 135 | [L'f'] = 0x00b0, 136 | [L'g'] = 0x00b1, 137 | [L'h'] = 0x2592, 138 | [L'i'] = 0x2603, 139 | [L'j'] = 0x2518, 140 | [L'k'] = 0x2510, 141 | [L'l'] = 0x250c, 142 | [L'm'] = 0x2514, 143 | [L'n'] = 0x253c, 144 | [L'o'] = 0x23ba, 145 | [L'p'] = 0x23bb, 146 | [L'q'] = 0x2500, 147 | [L'r'] = 0x23bc, 148 | [L's'] = 0x23bd, 149 | [L't'] = 0x251c, 150 | [L'u'] = 0x2524, 151 | [L'v'] = 0x2534, 152 | [L'w'] = 0x252c, 153 | [L'x'] = 0x2502, 154 | [L'y'] = 0x2264, 155 | [L'z'] = 0x2265, 156 | [L'_'] = L' ', 157 | [L'0'] = 0x25ae 158 | }; 159 | 160 | #else /* wchar_t doesn't map to Unicode... */ 161 | 162 | static wchar_t CSET_UK[] ={ /* "United Kingdom"...really just Pound Sterling */ 163 | [L'#'] = L'&' 164 | }; 165 | 166 | static wchar_t CSET_GRAPH[] ={ /* Graphics Set One */ 167 | [L'-'] = '^', 168 | [L'}'] = L'&', 169 | [L'~'] = L'o', 170 | [L'{'] = L'p', 171 | [L','] = L'<', 172 | [L'+'] = L'>', 173 | [L'.'] = L'v', 174 | [L'|'] = L'!', 175 | [L'>'] = L'>', 176 | [L'`'] = L'+', 177 | [L'a'] = L':', 178 | [L'b'] = L' ', 179 | [L'c'] = L' ', 180 | [L'd'] = L' ', 181 | [L'e'] = L' ', 182 | [L'f'] = L'\'', 183 | [L'g'] = L'#', 184 | [L'h'] = L'#', 185 | [L'i'] = L'i', 186 | [L'j'] = L'+', 187 | [L'k'] = L'+', 188 | [L'l'] = L'+', 189 | [L'm'] = L'+', 190 | [L'n'] = '+', 191 | [L'o'] = L'-', 192 | [L'p'] = L'-', 193 | [L'q'] = L'-', 194 | [L'r'] = L'-', 195 | [L's'] = L'_', 196 | [L't'] = L'+', 197 | [L'u'] = L'+', 198 | [L'v'] = L'+', 199 | [L'w'] = L'+', 200 | [L'x'] = L'|', 201 | [L'y'] = L'<', 202 | [L'z'] = L'>', 203 | [L'_'] = L' ', 204 | [L'0'] = L'#', 205 | }; 206 | 207 | #endif 208 | 209 | -------------------------------------------------------------------------------- /mtm.1: -------------------------------------------------------------------------------- 1 | .Dd $Mdocdate$ 2 | .Dt MTM 1 3 | .Os 4 | .Sh NAME 5 | .Nm mtm 6 | .Nd a micro-terminal multiplexer 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl T Ar HOST 10 | .Op Fl t Ar TERM 11 | .Op Fl c Ar CHARACTER 12 | .Sh DESCRIPTION 13 | .Nm 14 | is a terminal multiplexer, 15 | a program that allows multiple terminal sessions to run within a single 16 | (physical or virtual) 17 | terminal session. 18 | .Pp 19 | The options are 20 | .Bl -tag -width Ds 21 | .It Fl T Ar HOST 22 | Assume that the host terminal 23 | .Po 24 | i.e. the terminal under which 25 | .Nm 26 | is running 27 | .Pc 28 | is of type 29 | .Ar HOST "." 30 | By default, 31 | the contents of the 32 | .Ev TERM 33 | environment variable are used. 34 | .It Fl t Ar TERM 35 | Set the 36 | .Ev TERM 37 | environment variable within the mtm session to 38 | .Ar TERM "." 39 | By default, this is 40 | .Em "screen-bce" "," 41 | or 42 | .Em "screen-256color-bce" 43 | if 44 | .Nm 45 | detects that the host terminal supports 256 colors. 46 | Note that this default can be changed at compile time, 47 | and thus may differ in your installation. 48 | .It Fl c Ar CHARACTER 49 | Commands are prefixed using 50 | .Ar CHARACTER 51 | in combination with the 52 | .Dq control 53 | key. 54 | By default this is 55 | .Dq "g" "." 56 | Note that this default can be changed at compile time, 57 | and thus may differ in your installation. 58 | .El 59 | .Pp 60 | .Ss Usage 61 | .Nm 62 | divides the screen into multiple virtual terminals. 63 | Each virtual terminal is updated and accessed independently. 64 | At any given time, 65 | exactly one virtual terminal is 66 | .Dq focused "," 67 | meaning that it receives any typed characters. 68 | The currently-focused terminal is indicated by the location of the cursor. 69 | .Pp 70 | While running, 71 | the following keys are recognized as commands to 72 | .Nm 73 | when prefixed with the command character: 74 | .Bl -tag -width Ds 75 | .It Em "Up/Down/Right/Left Arrow" 76 | Select the terminal above/below/to the right of/to the left of the currently focused one. 77 | .It Em "c" 78 | Abandon the keychord sequence. 79 | .It Em "o" 80 | .Pq "the letter oh" 81 | Switch to the last-focused terminal. 82 | .It Em "h" "or" "v" 83 | Split the focused terminal in half and stack horizontally 84 | .Pq "for 'h'" 85 | or stack vertically 86 | .Pq "for 'v'" ";" 87 | the newly-created terminal will be focused. 88 | .It Em "w" 89 | Delete the currently focused terminal. 90 | .It Em "l" 91 | .Pq "the letter ell" 92 | Redraw the screen. 93 | .It Em "PgUp/PgDown/End" 94 | Scroll the terminal up/down/to the bottom. 95 | By default, 96 | .Nm 97 | stores up to 1000 lines of scrolling history. 98 | Note that if the screen is currently scrolled back 99 | .Pq "that is, the scrollback buffer is not at the bottom" 100 | these keys need not be prefixed with the command key. 101 | .Nm 102 | will also scroll to the bottom on user input. 103 | .El 104 | .Pp 105 | Note that these command keys can be changed at compile time, 106 | and therefore may be different for this installation of 107 | .Nm "." 108 | .Pp 109 | .Nm 110 | will exit when its last virtual terminal is closed. 111 | .Ss The Value of Fl t 112 | The terminal name passed to 113 | .Fl t 114 | does not change how 115 | .Nm 116 | interprets command sequences, 117 | but rather changes what kind of terminal 118 | .Nm 119 | claims to be. 120 | Safe bets for this value are 121 | .Bl -tag -width Ds 122 | .It mtm 123 | This is the terminal described by the 124 | .Pa mtm.ti 125 | .Xr terminfo 5 126 | description that shipped with 127 | .Nm "." 128 | It is not necessarily installed on your system, 129 | but if it is, it will tell 130 | .Xr terminfo 5 131 | -aware programs how to use all of the 132 | .Nm 133 | features. 134 | Note that there aren't any user-visible features added here, 135 | but 136 | .Nm 137 | supports some extra cursor-movement commands, 138 | and thus using this terminal type where available may result in slightly 139 | speedier screen updates. 140 | .It mtm-256color 141 | This is the same as the 142 | .Nm 143 | terminal type, but advertising 256-color support. 144 | This can be used if the 145 | .Nm 146 | .Xr terminfo 5 147 | description is installed and the host terminal supports 256 or more colors. 148 | .It screen-bce 149 | This is the default if 256-color support is not detected. 150 | It is widely supported and its description is installed by default on most systems. 151 | This is a good choice if the 152 | .Nm 153 | .Xr terminfo 5 154 | description is not installed everywhere you expect to use 155 | .Nm mtm "." 156 | .It screen-bce-256color 157 | This is the default if 256-color support is detected. 158 | The same advice given for 159 | .Em screen-bce 160 | above applies here too. 161 | .El 162 | .Ss The mtm Environment 163 | .Nm 164 | sets the 165 | .Ev MTM 166 | environment variable to its process ID 167 | .Po 168 | see 169 | .Xr getpid 2 170 | .Pc "." 171 | Programs can test for the presence of this variable to determine if they are 172 | running inside of a 173 | .Nm 174 | instance. 175 | .Sh ENVIRONMENT 176 | The following environment variables affect the operation of 177 | .Nm mtm ":" 178 | .Bl -tag -width Ds 179 | .It Ev TERM 180 | Names the hosting terminal's type. 181 | This can be overridden using the 182 | .Fl T 183 | option. 184 | .It Ev ESCDELAY 185 | This variable specifies the number of milliseconds 186 | .Nm 187 | will wait after seeing an escape character for a special character sequence to complete. 188 | By default this is 1000 189 | .Pq "one second" "." 190 | .It Ev LC_CTYPE Ev LC_ALL Ev LANG 191 | These variables are consulted to determine the encoding used for textual data. 192 | .It SHELL 193 | If set, 194 | .Nm 195 | will launch the program named by this command in new virtual terminals. 196 | If this is unset, 197 | .Nm 198 | will use the value of the 199 | .Dq "command interpreter" 200 | field of the password database 201 | .Po 202 | see 203 | .Xr passwd 5 204 | .Pc 205 | if available, 206 | and will finally fall back to 207 | .Pa "/bin/sh" "." 208 | .Sh SEE ALSO 209 | .Xr sh 1 210 | .Xr terminfo 5 211 | .Sh BUGS 212 | .Pp 213 | .Nm 214 | will attempt to fit all virtual terminals in the window at once. 215 | If many terminals are created and the containing window is shrunk too small, 216 | the display will be suboptimal. 217 | .Pp 218 | The only human language in which output is generated and in which documentation 219 | is available is English, 220 | regardless of the user's preferred language. 221 | -------------------------------------------------------------------------------- /mtm.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 - 2019 Rob King 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "vtparser.h" 33 | 34 | /*** CONFIGURATION */ 35 | #include "config.h" 36 | 37 | #define MIN(x, y) ((x) < (y)? (x) : (y)) 38 | #define MAX(x, y) ((x) > (y)? (x) : (y)) 39 | #define CTL(x) ((x) & 0x1f) 40 | #define USAGE "usage: mtm [-T NAME] [-t NAME] [-c KEY]\n" 41 | 42 | /*** DATA TYPES */ 43 | typedef enum{ 44 | HORIZONTAL, 45 | VERTICAL, 46 | VIEW 47 | } Node; 48 | 49 | typedef struct SCRN SCRN; 50 | struct SCRN{ 51 | int sy, sx, vis, tos, off; 52 | short fg, bg, sfg, sbg, sp; 53 | bool insert, oxenl, xenl, saved; 54 | attr_t sattr; 55 | WINDOW *win; 56 | }; 57 | 58 | typedef struct NODE NODE; 59 | struct NODE{ 60 | Node t; 61 | int y, x, h, w, pt, ntabs; 62 | bool *tabs, pnm, decom, am, lnm; 63 | wchar_t repc; 64 | NODE *p, *c1, *c2; 65 | SCRN pri, alt, *s; 66 | wchar_t *g0, *g1, *g2, *g3, *gc, *gs, *sgc, *sgs; 67 | VTPARSER vp; 68 | }; 69 | 70 | /*** GLOBALS AND PROTOTYPES */ 71 | static NODE *root, *focused, *lastfocused = NULL; 72 | static int commandkey = CTL(COMMAND_KEY), nfds = 1; /* stdin */ 73 | static fd_set fds; 74 | static char iobuf[BUFSIZ]; 75 | 76 | static void setupevents(NODE *n); 77 | static void reshape(NODE *n, int y, int x, int h, int w); 78 | static void draw(NODE *n); 79 | static void reshapechildren(NODE *n); 80 | static const char *term = NULL; 81 | static void freenode(NODE *n, bool recursive); 82 | void start_pairs(void); 83 | short mtm_alloc_pair(int fg, int bg); 84 | 85 | /*** UTILITY FUNCTIONS */ 86 | static void 87 | quit(int rc, const char *m) /* Shut down MTM. */ 88 | { 89 | if (m) 90 | fprintf(stderr, "%s\n", m); 91 | if (root) 92 | freenode(root, true); 93 | endwin(); 94 | exit(rc); 95 | } 96 | 97 | static void 98 | safewrite(int fd, const char *b, size_t n) /* Write, checking for errors. */ 99 | { 100 | size_t w = 0; 101 | while (w < n){ 102 | ssize_t s = write(fd, b + w, n - w); 103 | if (s < 0 && errno != EINTR) 104 | return; 105 | else if (s < 0) 106 | s = 0; 107 | w += (size_t)s; 108 | } 109 | } 110 | 111 | static const char * 112 | getshell(void) /* Get the user's preferred shell. */ 113 | { 114 | if (getenv("SHELL")) 115 | return getenv("SHELL"); 116 | struct passwd *pwd = getpwuid(getuid()); 117 | if (pwd) 118 | return pwd->pw_shell; 119 | return "/bin/sh"; 120 | } 121 | 122 | /*** TERMINAL EMULATION HANDLERS 123 | * These functions implement the various terminal commands activated by 124 | * escape sequences and printing to the terminal. Large amounts of boilerplate 125 | * code is shared among all these functions, and is factored out into the 126 | * macros below: 127 | * PD(n, d) - Parameter n, with default d. 128 | * P0(n) - Parameter n, default 0. 129 | * P1(n) - Parameter n, default 1. 130 | * CALL(h) - Call handler h with no arguments. 131 | * SENDN(n, s, c) - Write string c bytes of s to n. 132 | * SEND(n, s) - Write string s to node n's host. 133 | * (END)HANDLER - Declare/end a handler function 134 | * COMMONVARS - All of the common variables for a handler. 135 | * x, y - cursor position 136 | * mx, my - max possible values for x and y 137 | * px, py - physical cursor position in scrollback 138 | * n - the current node 139 | * win - the current window 140 | * top, bot - the scrolling region 141 | * tos - top of the screen in the pad 142 | * s - the current SCRN buffer 143 | * The funny names for handlers are from their ANSI/ECMA/DEC mnemonics. 144 | */ 145 | #define PD(x, d) (argc < (x) || !argv? (d) : argv[(x)]) 146 | #define P0(x) PD(x, 0) 147 | #define P1(x) (!P0(x)? 1 : P0(x)) 148 | #define CALL(x) (x)(v, n, 0, 0, 0, NULL, NULL) 149 | #define SENDN(n, s, c) safewrite(n->pt, s, c) 150 | #define SEND(n, s) SENDN(n, s, strlen(s)) 151 | #define COMMONVARS \ 152 | NODE *n = (NODE *)p; \ 153 | SCRN *s = n->s; \ 154 | WINDOW *win = s->win; \ 155 | int py, px, y, x, my, mx, top = 0, bot = 0, tos = s->tos; \ 156 | (void)v; (void)p; (void)w; (void)iw; (void)argc; (void)argv; \ 157 | (void)win; (void)y; (void)x; (void)my; (void)mx; (void)osc; \ 158 | (void)tos; \ 159 | getyx(win, py, px); y = py - s->tos; x = px; \ 160 | getmaxyx(win, my, mx); my -= s->tos; \ 161 | wgetscrreg(win, &top, &bot); \ 162 | bot++; bot -= s->tos; \ 163 | top = top <= tos? 0 : top - tos; \ 164 | 165 | #define HANDLER(name) \ 166 | static void \ 167 | name (VTPARSER *v, void *p, wchar_t w, wchar_t iw, \ 168 | int argc, int *argv, const wchar_t *osc) \ 169 | { COMMONVARS 170 | #define ENDHANDLER n->repc = 0; } /* control sequences aren't repeated */ 171 | 172 | HANDLER(bell) /* Terminal bell. */ 173 | beep(); 174 | ENDHANDLER 175 | 176 | HANDLER(numkp) /* Application/Numeric Keypad Mode */ 177 | n->pnm = (w == L'='); 178 | ENDHANDLER 179 | 180 | HANDLER(vis) /* Cursor visibility */ 181 | s->vis = iw == L'6'? 0 : 1; 182 | ENDHANDLER 183 | 184 | HANDLER(cup) /* CUP - Cursor Position */ 185 | s->xenl = false; 186 | wmove(win, tos + (n->decom? top : 0) + P1(0) - 1, P1(1) - 1); 187 | ENDHANDLER 188 | 189 | HANDLER(dch) /* DCH - Delete Character */ 190 | for (int i = 0; i < P1(0); i++) 191 | wdelch(win); 192 | ENDHANDLER 193 | 194 | HANDLER(ich) /* ICH - Insert Character */ 195 | for (int i = 0; i < P1(0); i++) 196 | wins_nwstr(win, L" ", 1); 197 | ENDHANDLER 198 | 199 | HANDLER(cuu) /* CUU - Cursor Up */ 200 | wmove(win, MAX(py - P1(0), tos + top), x); 201 | ENDHANDLER 202 | 203 | HANDLER(cud) /* CUD - Cursor Down */ 204 | wmove(win, MIN(py + P1(0), tos + bot - 1), x); 205 | ENDHANDLER 206 | 207 | HANDLER(cuf) /* CUF - Cursor Forward */ 208 | wmove(win, py, MIN(x + P1(0), mx - 1)); 209 | ENDHANDLER 210 | 211 | HANDLER(ack) /* ACK - Acknowledge Enquiry */ 212 | SEND(n, "\006"); 213 | ENDHANDLER 214 | 215 | HANDLER(hts) /* HTS - Horizontal Tab Set */ 216 | if (x < n->ntabs && x > 0) 217 | n->tabs[x] = true; 218 | ENDHANDLER 219 | 220 | HANDLER(ri) /* RI - Reverse Index */ 221 | int otop = 0, obot = 0; 222 | wgetscrreg(win, &otop, &obot); 223 | wsetscrreg(win, otop >= tos? otop : tos, obot); 224 | y == top? wscrl(win, -1) : wmove(win, MAX(tos, py - 1), x); 225 | wsetscrreg(win, otop, obot); 226 | ENDHANDLER 227 | 228 | HANDLER(decid) /* DECID - Send Terminal Identification */ 229 | if (w == L'c') 230 | SEND(n, iw == L'>'? "\033[>1;10;0c" : "\033[?1;2c"); 231 | else if (w == L'Z') 232 | SEND(n, "\033[?6c"); 233 | ENDHANDLER 234 | 235 | HANDLER(hpa) /* HPA - Cursor Horizontal Absolute */ 236 | wmove(win, py, MIN(P1(0) - 1, mx - 1)); 237 | ENDHANDLER 238 | 239 | HANDLER(hpr) /* HPR - Cursor Horizontal Relative */ 240 | wmove(win, py, MIN(px + P1(0), mx - 1)); 241 | ENDHANDLER 242 | 243 | HANDLER(vpa) /* VPA - Cursor Vertical Absolute */ 244 | wmove(win, MIN(tos + bot - 1, MAX(tos + top, tos + P1(0) - 1)), x); 245 | ENDHANDLER 246 | 247 | HANDLER(vpr) /* VPR - Cursor Vertical Relative */ 248 | wmove(win, MIN(tos + bot - 1, MAX(tos + top, py + P1(0))), x); 249 | ENDHANDLER 250 | 251 | HANDLER(cbt) /* CBT - Cursor Backwards Tab */ 252 | for (int i = x - 1; i < n->ntabs && i >= 0; i--) if (n->tabs[i]){ 253 | wmove(win, py, i); 254 | return; 255 | } 256 | wmove(win, py, 0); 257 | ENDHANDLER 258 | 259 | HANDLER(ht) /* HT - Horizontal Tab */ 260 | for (int i = x + 1; i < n->w && i < n->ntabs; i++) if (n->tabs[i]){ 261 | wmove(win, py, i); 262 | return; 263 | } 264 | wmove(win, py, mx - 1); 265 | ENDHANDLER 266 | 267 | HANDLER(tab) /* Tab forwards or backwards */ 268 | for (int i = 0; i < P1(0); i++) switch (w){ 269 | case L'I': CALL(ht); break; 270 | case L'\t': CALL(ht); break; 271 | case L'Z': CALL(cbt); break; 272 | } 273 | ENDHANDLER 274 | 275 | HANDLER(decaln) /* DECALN - Screen Alignment Test */ 276 | chtype e[] = {COLOR_PAIR(0) | 'E', 0}; 277 | for (int r = 0; r < my; r++){ 278 | for (int c = 0; c <= mx; c++) 279 | mvwaddchnstr(win, tos + r, c, e, 1); 280 | } 281 | wmove(win, py, px); 282 | ENDHANDLER 283 | 284 | HANDLER(su) /* SU - Scroll Up/Down */ 285 | wscrl(win, (w == L'T' || w == L'^')? -P1(0) : P1(0)); 286 | ENDHANDLER 287 | 288 | HANDLER(sc) /* SC - Save Cursor */ 289 | s->sx = px; /* save X position */ 290 | s->sy = py; /* save Y position */ 291 | wattr_get(win, &s->sattr, &s->sp, NULL); /* save attrs and color pair */ 292 | s->sfg = s->fg; /* save foreground color */ 293 | s->sbg = s->bg; /* save background color */ 294 | s->oxenl = s->xenl; /* save xenl state */ 295 | s->saved = true; /* save data is valid */ 296 | n->sgc = n->gc; n->sgs = n->gs; /* save character sets */ 297 | ENDHANDLER 298 | 299 | HANDLER(rc) /* RC - Restore Cursor */ 300 | if (iw == L'#'){ 301 | CALL(decaln); 302 | return; 303 | } 304 | if (!s->saved) 305 | return; 306 | wmove(win, s->sy, s->sx); /* get old position */ 307 | wattr_set(win, s->sattr, s->sp, NULL); /* get attrs and color pair */ 308 | s->fg = s->sfg; /* get foreground color */ 309 | s->bg = s->sbg; /* get background color */ 310 | s->xenl = s->oxenl; /* get xenl state */ 311 | n->gc = n->sgc; n->gs = n->sgs; /* save character sets */ 312 | 313 | /* restore colors */ 314 | int cp = mtm_alloc_pair(s->fg, s->bg); 315 | wcolor_set(win, cp, NULL); 316 | cchar_t c; 317 | setcchar(&c, L" ", A_NORMAL, cp, NULL); 318 | wbkgrndset(win, &c); 319 | ENDHANDLER 320 | 321 | HANDLER(tbc) /* TBC - Tabulation Clear */ 322 | switch (P0(0)){ 323 | case 0: n->tabs[x < n->ntabs? x : 0] = false; break; 324 | case 3: memset(n->tabs, 0, sizeof(bool) * (n->ntabs)); break; 325 | } 326 | ENDHANDLER 327 | 328 | HANDLER(cub) /* CUB - Cursor Backward */ 329 | s->xenl = false; 330 | wmove(win, py, MAX(x - P1(0), 0)); 331 | ENDHANDLER 332 | 333 | HANDLER(el) /* EL - Erase in Line */ 334 | cchar_t b; 335 | setcchar(&b, L" ", A_NORMAL, mtm_alloc_pair(s->fg, s->bg), NULL); 336 | switch (P0(0)){ 337 | case 0: wclrtoeol(win); break; 338 | case 1: for (int i = 0; i <= x; i++) mvwadd_wchnstr(win, py, i, &b, 1); break; 339 | case 2: wmove(win, py, 0); wclrtoeol(win); break; 340 | } 341 | wmove(win, py, x); 342 | ENDHANDLER 343 | 344 | HANDLER(ed) /* ED - Erase in Display */ 345 | int o = 1; 346 | switch (P0(0)){ 347 | case 0: wclrtobot(win); break; 348 | case 3: werase(win); break; 349 | case 2: wmove(win, tos, 0); wclrtobot(win); break; 350 | case 1: 351 | for (int i = tos; i < py; i++){ 352 | wmove(win, i, 0); 353 | wclrtoeol(win); 354 | } 355 | wmove(win, py, x); 356 | el(v, p, w, iw, 1, &o, NULL); 357 | break; 358 | } 359 | wmove(win, py, px); 360 | ENDHANDLER 361 | 362 | HANDLER(ech) /* ECH - Erase Character */ 363 | cchar_t c; 364 | setcchar(&c, L" ", A_NORMAL, mtm_alloc_pair(s->fg, s->bg), NULL); 365 | for (int i = 0; i < P1(0); i++) 366 | mvwadd_wchnstr(win, py, x + i, &c, 1); 367 | wmove(win, py, px); 368 | ENDHANDLER 369 | 370 | HANDLER(dsr) /* DSR - Device Status Report */ 371 | char buf[100] = {0}; 372 | if (P0(0) == 6) 373 | snprintf(buf, sizeof(buf) - 1, "\033[%d;%dR", 374 | (n->decom? y - top : y) + 1, x + 1); 375 | else 376 | snprintf(buf, sizeof(buf) - 1, "\033[0n"); 377 | SEND(n, buf); 378 | ENDHANDLER 379 | 380 | HANDLER(idl) /* IL or DL - Insert/Delete Line */ 381 | /* we don't use insdelln here because it inserts above and not below, 382 | * and has a few other edge cases... */ 383 | int otop = 0, obot = 0, p1 = MIN(P1(0), (my - 1) - y); 384 | wgetscrreg(win, &otop, &obot); 385 | wsetscrreg(win, py, obot); 386 | wscrl(win, w == L'L'? -p1 : p1); 387 | wsetscrreg(win, otop, obot); 388 | wmove(win, py, 0); 389 | ENDHANDLER 390 | 391 | HANDLER(csr) /* CSR - Change Scrolling Region */ 392 | if (wsetscrreg(win, tos + P1(0) - 1, tos + PD(1, my) - 1) == OK) 393 | CALL(cup); 394 | ENDHANDLER 395 | 396 | HANDLER(decreqtparm) /* DECREQTPARM - Request Device Parameters */ 397 | SEND(n, P0(0)? "\033[3;1;2;120;1;0x" : "\033[2;1;2;120;128;1;0x"); 398 | ENDHANDLER 399 | 400 | HANDLER(sgr0) /* Reset SGR to default */ 401 | wattrset(win, A_NORMAL); 402 | wcolor_set(win, 0, NULL); 403 | s->fg = s->bg = -1; 404 | wbkgdset(win, COLOR_PAIR(0) | ' '); 405 | ENDHANDLER 406 | 407 | HANDLER(cls) /* Clear screen */ 408 | CALL(cup); 409 | wclrtobot(win); 410 | CALL(cup); 411 | ENDHANDLER 412 | 413 | HANDLER(ris) /* RIS - Reset to Initial State */ 414 | n->gs = n->gc = n->g0 = CSET_US; n->g1 = CSET_GRAPH; 415 | n->g2 = CSET_US; n->g3 = CSET_GRAPH; 416 | n->decom = s->insert = s->oxenl = s->xenl = n->lnm = false; 417 | CALL(cls); 418 | CALL(sgr0); 419 | n->am = true; 420 | n->pnm = false; 421 | n->pri.vis = n->alt.vis = 1; 422 | n->s = &n->pri; 423 | wsetscrreg(n->pri.win, 0, MAX(SCROLLBACK, n->h) - 1); 424 | wsetscrreg(n->alt.win, 0, n->h - 1); 425 | for (int i = 0; i < n->ntabs; i++) 426 | n->tabs[i] = (i % 8 == 0); 427 | ENDHANDLER 428 | 429 | HANDLER(mode) /* Set or Reset Mode */ 430 | bool set = (w == L'h'); 431 | for (int i = 0; i < argc; i++) switch (P0(i)){ 432 | case 1: n->pnm = set; break; 433 | case 3: CALL(cls); break; 434 | case 4: s->insert = set; break; 435 | case 6: n->decom = set; CALL(cup); break; 436 | case 7: n->am = set; break; 437 | case 20: n->lnm = set; break; 438 | case 25: s->vis = set? 1 : 0; break; 439 | case 34: s->vis = set? 1 : 2; break; 440 | case 1048: CALL((set? sc : rc)); break; 441 | case 1049: 442 | CALL((set? sc : rc)); /* fall-through */ 443 | case 47: case 1047: if (set && n->s != &n->alt){ 444 | n->s = &n->alt; 445 | CALL(cls); 446 | } else if (!set && n->s != &n->pri) 447 | n->s = &n->pri; 448 | break; 449 | } 450 | ENDHANDLER 451 | 452 | HANDLER(sgr) /* SGR - Select Graphic Rendition */ 453 | bool doc = false, do8 = COLORS >= 8, do16 = COLORS >= 16, do256 = COLORS >= 256; 454 | if (!argc) 455 | CALL(sgr0); 456 | 457 | short bg = s->bg, fg = s->fg; 458 | for (int i = 0; i < argc; i++) switch (P0(i)){ 459 | case 0: CALL(sgr0); break; 460 | case 1: wattron(win, A_BOLD); break; 461 | case 2: wattron(win, A_DIM); break; 462 | case 4: wattron(win, A_UNDERLINE); break; 463 | case 5: wattron(win, A_BLINK); break; 464 | case 7: wattron(win, A_REVERSE); break; 465 | case 8: wattron(win, A_INVIS); break; 466 | case 22: wattroff(win, A_DIM); wattroff(win, A_BOLD); break; 467 | case 24: wattroff(win, A_UNDERLINE); break; 468 | case 25: wattroff(win, A_BLINK); break; 469 | case 27: wattroff(win, A_REVERSE); break; 470 | case 30: fg = COLOR_BLACK; doc = do8; break; 471 | case 31: fg = COLOR_RED; doc = do8; break; 472 | case 32: fg = COLOR_GREEN; doc = do8; break; 473 | case 33: fg = COLOR_YELLOW; doc = do8; break; 474 | case 34: fg = COLOR_BLUE; doc = do8; break; 475 | case 35: fg = COLOR_MAGENTA; doc = do8; break; 476 | case 36: fg = COLOR_CYAN; doc = do8; break; 477 | case 37: fg = COLOR_WHITE; doc = do8; break; 478 | case 38: fg = P0(i+1) == 5? P0(i+2) : s->fg; i += 2; doc = do256; break; 479 | case 39: fg = -1; doc = true; break; 480 | case 40: bg = COLOR_BLACK; doc = do8; break; 481 | case 41: bg = COLOR_RED; doc = do8; break; 482 | case 42: bg = COLOR_GREEN; doc = do8; break; 483 | case 43: bg = COLOR_YELLOW; doc = do8; break; 484 | case 44: bg = COLOR_BLUE; doc = do8; break; 485 | case 45: bg = COLOR_MAGENTA; doc = do8; break; 486 | case 46: bg = COLOR_CYAN; doc = do8; break; 487 | case 47: bg = COLOR_WHITE; doc = do8; break; 488 | case 48: bg = P0(i+1) == 5? P0(i+2) : s->bg; i += 2; doc = do256; break; 489 | case 49: bg = -1; doc = true; break; 490 | case 90: fg = COLOR_BLACK; doc = do16; break; 491 | case 91: fg = COLOR_RED; doc = do16; break; 492 | case 92: fg = COLOR_GREEN; doc = do16; break; 493 | case 93: fg = COLOR_YELLOW; doc = do16; break; 494 | case 94: fg = COLOR_BLUE; doc = do16; break; 495 | case 95: fg = COLOR_MAGENTA; doc = do16; break; 496 | case 96: fg = COLOR_CYAN; doc = do16; break; 497 | case 97: fg = COLOR_WHITE; doc = do16; break; 498 | case 100: bg = COLOR_BLACK; doc = do16; break; 499 | case 101: bg = COLOR_RED; doc = do16; break; 500 | case 102: bg = COLOR_GREEN; doc = do16; break; 501 | case 103: bg = COLOR_YELLOW; doc = do16; break; 502 | case 104: bg = COLOR_BLUE; doc = do16; break; 503 | case 105: bg = COLOR_MAGENTA; doc = do16; break; 504 | case 106: bg = COLOR_CYAN; doc = do16; break; 505 | case 107: bg = COLOR_WHITE; doc = do16; break; 506 | #if defined(A_ITALIC) && !defined(NO_ITALICS) && !defined(REVERSE_ITALICS) 507 | case 3: wattron(win, A_ITALIC); break; 508 | case 23: wattroff(win, A_ITALIC); break; 509 | #endif 510 | #if defined(REVERSE_ITALICS) 511 | case 3: wattron(win, A_REVERSE); break; 512 | case 23: wattroff(win, A_REVERSE); break; 513 | #endif 514 | } 515 | if (doc){ 516 | int p = mtm_alloc_pair(s->fg = fg, s->bg = bg); 517 | wcolor_set(win, p, NULL); 518 | cchar_t c; 519 | setcchar(&c, L" ", A_NORMAL, p, NULL); 520 | wbkgrndset(win, &c); 521 | } 522 | } 523 | 524 | HANDLER(cr) /* CR - Carriage Return */ 525 | s->xenl = false; 526 | wmove(win, py, 0); 527 | ENDHANDLER 528 | 529 | HANDLER(ind) /* IND - Index */ 530 | y == (bot - 1)? scroll(win) : wmove(win, py + 1, x); 531 | ENDHANDLER 532 | 533 | HANDLER(nel) /* NEL - Next Line */ 534 | CALL(cr); CALL(ind); 535 | ENDHANDLER 536 | 537 | HANDLER(pnl) /* NL - Newline */ 538 | CALL((n->lnm? nel : ind)); 539 | ENDHANDLER 540 | 541 | HANDLER(cpl) /* CPL - Cursor Previous Line */ 542 | wmove(win, MAX(tos + top, py - P1(0)), 0); 543 | ENDHANDLER 544 | 545 | HANDLER(cnl) /* CNL - Cursor Next Line */ 546 | wmove(win, MIN(tos + bot - 1, py + P1(0)), 0); 547 | ENDHANDLER 548 | 549 | HANDLER(print) /* Print a character to the terminal */ 550 | if (wcwidth(w) < 0) 551 | return; 552 | 553 | if (s->insert) 554 | CALL(ich); 555 | 556 | if (s->xenl){ 557 | s->xenl = false; 558 | if (n->am) 559 | CALL(nel); 560 | getyx(win, y, x); 561 | y -= tos; 562 | } 563 | 564 | if (w < MAXMAP && n->gc[w]) 565 | w = n->gc[w]; 566 | n->repc = w; 567 | 568 | if (x == mx - wcwidth(w)){ 569 | s->xenl = true; 570 | wins_nwstr(win, &w, 1); 571 | } else 572 | waddnwstr(win, &w, 1); 573 | n->gc = n->gs; 574 | } /* no ENDHANDLER because we don't want to reset repc */ 575 | 576 | HANDLER(rep) /* REP - Repeat Character */ 577 | for (int i = 0; i < P1(0) && n->repc; i++) 578 | print(v, p, n->repc, 0, 0, NULL, NULL); 579 | ENDHANDLER 580 | 581 | HANDLER(scs) /* Select Character Set */ 582 | wchar_t **t = NULL; 583 | switch (iw){ 584 | case L'(': t = &n->g0; break; 585 | case L')': t = &n->g1; break; 586 | case L'*': t = &n->g2; break; 587 | case L'+': t = &n->g3; break; 588 | default: return; break; 589 | } 590 | switch (w){ 591 | case L'A': *t = CSET_UK; break; 592 | case L'B': *t = CSET_US; break; 593 | case L'0': *t = CSET_GRAPH; break; 594 | case L'1': *t = CSET_US; break; 595 | case L'2': *t = CSET_GRAPH; break; 596 | } 597 | ENDHANDLER 598 | 599 | HANDLER(so) /* Switch Out/In Character Set */ 600 | if (w == 0x0e) 601 | n->gs = n->gc = n->g1; /* locking shift */ 602 | else if (w == 0xf) 603 | n->gs = n->gc = n->g0; /* locking shift */ 604 | else if (w == L'n') 605 | n->gs = n->gc = n->g2; /* locking shift */ 606 | else if (w == L'o') 607 | n->gs = n->gc = n->g3; /* locking shift */ 608 | else if (w == L'N'){ 609 | n->gs = n->gc; /* non-locking shift */ 610 | n->gc = n->g2; 611 | } else if (w == L'O'){ 612 | n->gs = n->gc; /* non-locking shift */ 613 | n->gc = n->g3; 614 | } 615 | ENDHANDLER 616 | 617 | static void 618 | setupevents(NODE *n) 619 | { 620 | n->vp.p = n; 621 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x05, ack); 622 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x07, bell); 623 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x08, cub); 624 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x09, tab); 625 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0a, pnl); 626 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0b, pnl); 627 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0c, pnl); 628 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0d, cr); 629 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0e, so); 630 | vtonevent(&n->vp, VTPARSER_CONTROL, 0x0f, so); 631 | vtonevent(&n->vp, VTPARSER_CSI, L'A', cuu); 632 | vtonevent(&n->vp, VTPARSER_CSI, L'B', cud); 633 | vtonevent(&n->vp, VTPARSER_CSI, L'C', cuf); 634 | vtonevent(&n->vp, VTPARSER_CSI, L'D', cub); 635 | vtonevent(&n->vp, VTPARSER_CSI, L'E', cnl); 636 | vtonevent(&n->vp, VTPARSER_CSI, L'F', cpl); 637 | vtonevent(&n->vp, VTPARSER_CSI, L'G', hpa); 638 | vtonevent(&n->vp, VTPARSER_CSI, L'H', cup); 639 | vtonevent(&n->vp, VTPARSER_CSI, L'I', tab); 640 | vtonevent(&n->vp, VTPARSER_CSI, L'J', ed); 641 | vtonevent(&n->vp, VTPARSER_CSI, L'K', el); 642 | vtonevent(&n->vp, VTPARSER_CSI, L'L', idl); 643 | vtonevent(&n->vp, VTPARSER_CSI, L'M', idl); 644 | vtonevent(&n->vp, VTPARSER_CSI, L'P', dch); 645 | vtonevent(&n->vp, VTPARSER_CSI, L'S', su); 646 | vtonevent(&n->vp, VTPARSER_CSI, L'T', su); 647 | vtonevent(&n->vp, VTPARSER_CSI, L'X', ech); 648 | vtonevent(&n->vp, VTPARSER_CSI, L'Z', tab); 649 | vtonevent(&n->vp, VTPARSER_CSI, L'`', hpa); 650 | vtonevent(&n->vp, VTPARSER_CSI, L'^', su); 651 | vtonevent(&n->vp, VTPARSER_CSI, L'@', ich); 652 | vtonevent(&n->vp, VTPARSER_CSI, L'a', hpr); 653 | vtonevent(&n->vp, VTPARSER_CSI, L'b', rep); 654 | vtonevent(&n->vp, VTPARSER_CSI, L'c', decid); 655 | vtonevent(&n->vp, VTPARSER_CSI, L'd', vpa); 656 | vtonevent(&n->vp, VTPARSER_CSI, L'e', vpr); 657 | vtonevent(&n->vp, VTPARSER_CSI, L'f', cup); 658 | vtonevent(&n->vp, VTPARSER_CSI, L'g', tbc); 659 | vtonevent(&n->vp, VTPARSER_CSI, L'h', mode); 660 | vtonevent(&n->vp, VTPARSER_CSI, L'l', mode); 661 | vtonevent(&n->vp, VTPARSER_CSI, L'm', sgr); 662 | vtonevent(&n->vp, VTPARSER_CSI, L'n', dsr); 663 | vtonevent(&n->vp, VTPARSER_CSI, L'r', csr); 664 | vtonevent(&n->vp, VTPARSER_CSI, L's', sc); 665 | vtonevent(&n->vp, VTPARSER_CSI, L'u', rc); 666 | vtonevent(&n->vp, VTPARSER_CSI, L'x', decreqtparm); 667 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'0', scs); 668 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'1', scs); 669 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'2', scs); 670 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'7', sc); 671 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'8', rc); 672 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'A', scs); 673 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'B', scs); 674 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'D', ind); 675 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'E', nel); 676 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'H', hts); 677 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'M', ri); 678 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'Z', decid); 679 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'c', ris); 680 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'p', vis); 681 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'=', numkp); 682 | vtonevent(&n->vp, VTPARSER_ESCAPE, L'>', numkp); 683 | vtonevent(&n->vp, VTPARSER_PRINT, 0, print); 684 | } 685 | 686 | /*** MTM FUNCTIONS 687 | * These functions do the user-visible work of MTM: creating nodes in the 688 | * tree, updating the display, and so on. 689 | */ 690 | static bool * 691 | newtabs(int w, int ow, bool *oldtabs) /* Initialize default tabstops. */ 692 | { 693 | bool *tabs = calloc(w, sizeof(bool)); 694 | if (!tabs) 695 | return NULL; 696 | for (int i = 0; i < w; i++) /* keep old overlapping tabs */ 697 | tabs[i] = i < ow? oldtabs[i] : (i % 8 == 0); 698 | return tabs; 699 | } 700 | 701 | static NODE * 702 | newnode(Node t, NODE *p, int y, int x, int h, int w) /* Create a new node. */ 703 | { 704 | NODE *n = calloc(1, sizeof(NODE)); 705 | bool *tabs = newtabs(w, 0, NULL); 706 | if (!n || h < 2 || w < 2 || !tabs) 707 | return free(n), free(tabs), NULL; 708 | 709 | n->t = t; 710 | n->pt = -1; 711 | n->p = p; 712 | n->y = y; 713 | n->x = x; 714 | n->h = h; 715 | n->w = w; 716 | n->tabs = tabs; 717 | n->ntabs = w; 718 | 719 | return n; 720 | } 721 | 722 | static void 723 | freenode(NODE *n, bool recurse) /* Free a node. */ 724 | { 725 | if (n){ 726 | if (lastfocused == n) 727 | lastfocused = NULL; 728 | if (n->pri.win) 729 | delwin(n->pri.win); 730 | if (n->alt.win) 731 | delwin(n->alt.win); 732 | if (recurse) 733 | freenode(n->c1, true); 734 | if (recurse) 735 | freenode(n->c2, true); 736 | if (n->pt >= 0){ 737 | close(n->pt); 738 | FD_CLR(n->pt, &fds); 739 | } 740 | free(n->tabs); 741 | free(n); 742 | } 743 | } 744 | 745 | static void 746 | fixcursor(void) /* Move the terminal cursor to the active view. */ 747 | { 748 | if (focused){ 749 | int y, x; 750 | curs_set(focused->s->off != focused->s->tos? 0 : focused->s->vis); 751 | getyx(focused->s->win, y, x); 752 | y = MIN(MAX(y, focused->s->tos), focused->s->tos + focused->h - 1); 753 | wmove(focused->s->win, y, x); 754 | } 755 | } 756 | 757 | static const char * 758 | getterm(void) 759 | { 760 | const char *envterm = getenv("TERM"); 761 | if (term) 762 | return term; 763 | if (envterm && COLORS >= 256 && !strstr(DEFAULT_TERMINAL, "-256color")) 764 | return DEFAULT_256_COLOR_TERMINAL; 765 | return DEFAULT_TERMINAL; 766 | } 767 | 768 | static NODE * 769 | newview(NODE *p, int y, int x, int h, int w) /* Open a new view. */ 770 | { 771 | struct winsize ws = {.ws_row = h, .ws_col = w}; 772 | NODE *n = newnode(VIEW, p, y, x, h, w); 773 | if (!n) 774 | return NULL; 775 | 776 | SCRN *pri = &n->pri, *alt = &n->alt; 777 | pri->win = newpad(MAX(h, SCROLLBACK), w); 778 | alt->win = newpad(h, w); 779 | if (!pri->win || !alt->win) 780 | return freenode(n, false), NULL; 781 | pri->tos = pri->off = MAX(0, SCROLLBACK - h); 782 | n->s = pri; 783 | 784 | nodelay(pri->win, TRUE); nodelay(alt->win, TRUE); 785 | scrollok(pri->win, TRUE); scrollok(alt->win, TRUE); 786 | keypad(pri->win, TRUE); keypad(alt->win, TRUE); 787 | 788 | setupevents(n); 789 | ris(&n->vp, n, L'c', 0, 0, NULL, NULL); 790 | 791 | pid_t pid = forkpty(&n->pt, NULL, NULL, &ws); 792 | if (pid < 0){ 793 | if (!p) 794 | perror("forkpty"); 795 | return freenode(n, false), NULL; 796 | } else if (pid == 0){ 797 | char buf[100] = {0}; 798 | snprintf(buf, sizeof(buf) - 1, "%lu", (unsigned long)getppid()); 799 | setsid(); 800 | setenv("MTM", buf, 1); 801 | setenv("TERM", getterm(), 1); 802 | signal(SIGCHLD, SIG_DFL); 803 | execl(getshell(), getshell(), NULL); 804 | return NULL; 805 | } 806 | 807 | FD_SET(n->pt, &fds); 808 | fcntl(n->pt, F_SETFL, O_NONBLOCK); 809 | nfds = n->pt > nfds? n->pt : nfds; 810 | return n; 811 | } 812 | 813 | static NODE * 814 | newcontainer(Node t, NODE *p, int y, int x, int h, int w, 815 | NODE *c1, NODE *c2) /* Create a new container */ 816 | { 817 | NODE *n = newnode(t, p, y, x, h, w); 818 | if (!n) 819 | return NULL; 820 | 821 | n->c1 = c1; 822 | n->c2 = c2; 823 | c1->p = c2->p = n; 824 | 825 | reshapechildren(n); 826 | return n; 827 | } 828 | 829 | static void 830 | focus(NODE *n) /* Focus a node. */ 831 | { 832 | if (!n) 833 | return; 834 | else if (n->t == VIEW){ 835 | lastfocused = focused; 836 | focused = n; 837 | } else 838 | focus(n->c1? n->c1 : n->c2); 839 | } 840 | 841 | #define ABOVE(n) n->y - 2, n->x + n->w / 2 842 | #define BELOW(n) n->y + n->h + 2, n->x + n->w / 2 843 | #define LEFT(n) n->y + n->h / 2, n->x - 2 844 | #define RIGHT(n) n->y + n->h / 2, n->x + n->w + 2 845 | 846 | static NODE * 847 | findnode(NODE *n, int y, int x) /* Find the node enclosing y,x. */ 848 | { 849 | #define IN(n, y, x) (y >= n->y && y <= n->y + n->h && \ 850 | x >= n->x && x <= n->x + n->w) 851 | if (IN(n, y, x)){ 852 | if (n->c1 && IN(n->c1, y, x)) 853 | return findnode(n->c1, y, x); 854 | if (n->c2 && IN(n->c2, y, x)) 855 | return findnode(n->c2, y, x); 856 | return n; 857 | } 858 | return NULL; 859 | } 860 | 861 | static void 862 | replacechild(NODE *n, NODE *c1, NODE *c2) /* Replace c1 of n with c2. */ 863 | { 864 | c2->p = n; 865 | if (!n){ 866 | root = c2; 867 | reshape(c2, 0, 0, LINES, COLS); 868 | } else if (n->c1 == c1) 869 | n->c1 = c2; 870 | else if (n->c2 == c1) 871 | n->c2 = c2; 872 | 873 | n = n? n : root; 874 | reshape(n, n->y, n->x, n->h, n->w); 875 | draw(n); 876 | } 877 | 878 | static void 879 | removechild(NODE *p, const NODE *c) /* Replace p with other child. */ 880 | { 881 | replacechild(p->p, p, c == p->c1? p->c2 : p->c1); 882 | freenode(p, false); 883 | } 884 | 885 | static void 886 | deletenode(NODE *n) /* Delete a node. */ 887 | { 888 | if (!n || !n->p) 889 | quit(EXIT_SUCCESS, NULL); 890 | if (n == focused) 891 | focus(n->p->c1 == n? n->p->c2 : n->p->c1); 892 | removechild(n->p, n); 893 | freenode(n, true); 894 | } 895 | 896 | static void 897 | reshapeview(NODE *n, int d, int ow) /* Reshape a view. */ 898 | { 899 | int oy, ox; 900 | bool *tabs = newtabs(n->w, ow, n->tabs); 901 | struct winsize ws = {.ws_row = n->h, .ws_col = n->w}; 902 | 903 | if (tabs){ 904 | free(n->tabs); 905 | n->tabs = tabs; 906 | n->ntabs = n->w; 907 | } 908 | 909 | getyx(n->s->win, oy, ox); 910 | wresize(n->pri.win, MAX(n->h, SCROLLBACK), MAX(n->w, 2)); 911 | wresize(n->alt.win, MAX(n->h, 2), MAX(n->w, 2)); 912 | n->pri.tos = n->pri.off = MAX(0, SCROLLBACK - n->h); 913 | n->alt.tos = n->alt.off = 0; 914 | wsetscrreg(n->pri.win, 0, MAX(SCROLLBACK, n->h) - 1); 915 | wsetscrreg(n->alt.win, 0, n->h - 1); 916 | if (d > 0){ /* make sure the new top line syncs up after reshape */ 917 | wmove(n->s->win, oy + d, ox); 918 | wscrl(n->s->win, -d); 919 | } 920 | doupdate(); 921 | refresh(); 922 | ioctl(n->pt, TIOCSWINSZ, &ws); 923 | } 924 | 925 | static void 926 | reshapechildren(NODE *n) /* Reshape all children of a view. */ 927 | { 928 | if (n->t == HORIZONTAL){ 929 | int i = n->w % 2? 0 : 1; 930 | reshape(n->c1, n->y, n->x, n->h, n->w / 2); 931 | reshape(n->c2, n->y, n->x + n->w / 2 + 1, n->h, n->w / 2 - i); 932 | } else if (n->t == VERTICAL){ 933 | int i = n->h % 2? 0 : 1; 934 | reshape(n->c1, n->y, n->x, n->h / 2, n->w); 935 | reshape(n->c2, n->y + n->h / 2 + 1, n->x, n->h / 2 - i, n->w); 936 | } 937 | } 938 | 939 | static void 940 | reshape(NODE *n, int y, int x, int h, int w) /* Reshape a node. */ 941 | { 942 | if (n->y == y && n->x == x && n->h == h && n->w == w && n->t == VIEW) 943 | return; 944 | 945 | int d = n->h - h; 946 | int ow = n->w; 947 | n->y = y; 948 | n->x = x; 949 | n->h = MAX(h, 1); 950 | n->w = MAX(w, 1); 951 | 952 | if (n->t == VIEW) 953 | reshapeview(n, d, ow); 954 | else 955 | reshapechildren(n); 956 | draw(n); 957 | } 958 | 959 | static void 960 | drawchildren(const NODE *n) /* Draw all children of n. */ 961 | { 962 | draw(n->c1); 963 | if (n->t == HORIZONTAL) 964 | mvvline(n->y, n->x + n->w / 2, ACS_VLINE, n->h); 965 | else 966 | mvhline(n->y + n->h / 2, n->x, ACS_HLINE, n->w); 967 | wnoutrefresh(stdscr); 968 | draw(n->c2); 969 | } 970 | 971 | static void 972 | draw(NODE *n) /* Draw a node. */ 973 | { 974 | if (n->t == VIEW) 975 | pnoutrefresh(n->s->win, n->s->off, 0, n->y, n->x, 976 | n->y + n->h - 1, n->x + n->w - 1); 977 | else 978 | drawchildren(n); 979 | } 980 | 981 | static void 982 | split(NODE *n, Node t) /* Split a node. */ 983 | { 984 | int nh = t == VERTICAL? (n->h - 1) / 2 : n->h; 985 | int nw = t == HORIZONTAL? (n->w) / 2 : n->w; 986 | NODE *p = n->p; 987 | NODE *v = newview(NULL, 0, 0, MAX(0, nh), MAX(0, nw)); 988 | if (!v) 989 | return; 990 | 991 | NODE *c = newcontainer(t, n->p, n->y, n->x, n->h, n->w, n, v); 992 | if (!c){ 993 | freenode(v, false); 994 | return; 995 | } 996 | 997 | replacechild(p, n, c); 998 | focus(v); 999 | draw(p? p : root); 1000 | } 1001 | 1002 | static bool 1003 | getinput(NODE *n, fd_set *f) /* Recursively check all ptty's for input. */ 1004 | { 1005 | if (n && n->c1 && !getinput(n->c1, f)) 1006 | return false; 1007 | 1008 | if (n && n->c2 && !getinput(n->c2, f)) 1009 | return false; 1010 | 1011 | if (n && n->t == VIEW && n->pt > 0 && FD_ISSET(n->pt, f)){ 1012 | ssize_t r = read(n->pt, iobuf, sizeof(iobuf)); 1013 | if (r > 0) 1014 | vtwrite(&n->vp, iobuf, r); 1015 | if (r <= 0 && errno != EINTR && errno != EWOULDBLOCK) 1016 | return deletenode(n), false; 1017 | } 1018 | 1019 | return true; 1020 | } 1021 | 1022 | static void 1023 | scrollback(NODE *n) 1024 | { 1025 | n->s->off = MAX(0, n->s->off - n->h / 2); 1026 | } 1027 | 1028 | static void 1029 | scrollforward(NODE *n) 1030 | { 1031 | n->s->off = MIN(n->s->tos, n->s->off + n->h / 2); 1032 | } 1033 | 1034 | static void 1035 | scrollbottom(NODE *n) 1036 | { 1037 | n->s->off = n->s->tos; 1038 | } 1039 | 1040 | static void 1041 | sendarrow(const NODE *n, const char *k) 1042 | { 1043 | char buf[100] = {0}; 1044 | snprintf(buf, sizeof(buf) - 1, "\033%s%s", n->pnm? "O" : "[", k); 1045 | SEND(n, buf); 1046 | } 1047 | 1048 | static bool 1049 | handlechar(int r, int k) /* Handle a single input character. */ 1050 | { 1051 | const char cmdstr[] = {commandkey, 0}; 1052 | static bool cmd = false; 1053 | NODE *n = focused; 1054 | #define KERR(i) (r == ERR && (i) == k) 1055 | #define KEY(i) (r == OK && (i) == k) 1056 | #define CODE(i) (r == KEY_CODE_YES && (i) == k) 1057 | #define INSCR (n->s->tos != n->s->off) 1058 | #define SB scrollbottom(n) 1059 | #define DO(s, t, a) \ 1060 | if (s == cmd && (t)) { a ; cmd = false; return true; } 1061 | 1062 | DO(cmd, KERR(k), return false) 1063 | DO(cmd, CODE(KEY_RESIZE), reshape(root, 0, 0, LINES, COLS); SB) 1064 | DO(false, KEY(commandkey), return cmd = true) 1065 | DO(false, KEY(0), SENDN(n, "\000", 1); SB) 1066 | DO(false, KEY(L'\n'), SEND(n, "\n"); SB) 1067 | DO(false, KEY(L'\r'), SEND(n, n->lnm? "\r\n" : "\r"); SB) 1068 | DO(false, SCROLLUP && INSCR, scrollback(n)) 1069 | DO(false, SCROLLDOWN && INSCR, scrollforward(n)) 1070 | DO(false, RECENTER && INSCR, scrollbottom(n)) 1071 | DO(false, CODE(KEY_ENTER), SEND(n, n->lnm? "\r\n" : "\r"); SB) 1072 | DO(false, CODE(KEY_UP), sendarrow(n, "A"); SB); 1073 | DO(false, CODE(KEY_DOWN), sendarrow(n, "B"); SB); 1074 | DO(false, CODE(KEY_RIGHT), sendarrow(n, "C"); SB); 1075 | DO(false, CODE(KEY_LEFT), sendarrow(n, "D"); SB); 1076 | DO(false, CODE(KEY_HOME), SEND(n, "\033[1~"); SB) 1077 | DO(false, CODE(KEY_END), SEND(n, "\033[4~"); SB) 1078 | DO(false, CODE(KEY_PPAGE), SEND(n, "\033[5~"); SB) 1079 | DO(false, CODE(KEY_NPAGE), SEND(n, "\033[6~"); SB) 1080 | DO(false, CODE(KEY_BACKSPACE), SEND(n, "\177"); SB) 1081 | DO(false, CODE(KEY_DC), SEND(n, "\033[3~"); SB) 1082 | DO(false, CODE(KEY_IC), SEND(n, "\033[2~"); SB) 1083 | DO(false, CODE(KEY_BTAB), SEND(n, "\033[Z"); SB) 1084 | DO(false, CODE(KEY_F(1)), SEND(n, "\033OP"); SB) 1085 | DO(false, CODE(KEY_F(2)), SEND(n, "\033OQ"); SB) 1086 | DO(false, CODE(KEY_F(3)), SEND(n, "\033OR"); SB) 1087 | DO(false, CODE(KEY_F(4)), SEND(n, "\033OS"); SB) 1088 | DO(false, CODE(KEY_F(5)), SEND(n, "\033[15~"); SB) 1089 | DO(false, CODE(KEY_F(6)), SEND(n, "\033[17~"); SB) 1090 | DO(false, CODE(KEY_F(7)), SEND(n, "\033[18~"); SB) 1091 | DO(false, CODE(KEY_F(8)), SEND(n, "\033[19~"); SB) 1092 | DO(false, CODE(KEY_F(9)), SEND(n, "\033[20~"); SB) 1093 | DO(false, CODE(KEY_F(10)), SEND(n, "\033[21~"); SB) 1094 | DO(false, CODE(KEY_F(11)), SEND(n, "\033[23~"); SB) 1095 | DO(false, CODE(KEY_F(12)), SEND(n, "\033[24~"); SB) 1096 | DO(true, MOVE_UP, focus(findnode(root, ABOVE(n)))) 1097 | DO(true, MOVE_DOWN, focus(findnode(root, BELOW(n)))) 1098 | DO(true, MOVE_LEFT, focus(findnode(root, LEFT(n)))) 1099 | DO(true, MOVE_RIGHT, focus(findnode(root, RIGHT(n)))) 1100 | DO(true, MOVE_OTHER, focus(lastfocused)) 1101 | DO(true, HSPLIT, split(n, HORIZONTAL)) 1102 | DO(true, VSPLIT, split(n, VERTICAL)) 1103 | DO(true, DELETE_NODE, deletenode(n)) 1104 | DO(true, BAILOUT, (void)1) 1105 | DO(true, NUKE, wclear(n->s->win)) 1106 | DO(true, REDRAW, touchwin(stdscr); draw(root); redrawwin(stdscr)) 1107 | DO(true, SCROLLUP, scrollback(n)) 1108 | DO(true, SCROLLDOWN, scrollforward(n)) 1109 | DO(true, RECENTER, scrollbottom(n)) 1110 | DO(true, KEY(commandkey), SENDN(n, cmdstr, 1)); 1111 | char c[MB_LEN_MAX + 1] = {0}; 1112 | if (wctomb(c, k) > 0){ 1113 | scrollbottom(n); 1114 | SEND(n, c); 1115 | } 1116 | return cmd = false, true; 1117 | } 1118 | 1119 | static void 1120 | run(void) /* Run MTM. */ 1121 | { 1122 | while (root){ 1123 | wint_t w = 0; 1124 | fd_set sfds = fds; 1125 | if (select(nfds + 1, &sfds, NULL, NULL, NULL) < 0) 1126 | FD_ZERO(&sfds); 1127 | 1128 | int r = wget_wch(focused->s->win, &w); 1129 | while (handlechar(r, w)) 1130 | r = wget_wch(focused->s->win, &w); 1131 | getinput(root, &sfds); 1132 | 1133 | draw(root); 1134 | doupdate(); 1135 | fixcursor(); 1136 | draw(focused); 1137 | doupdate(); 1138 | } 1139 | } 1140 | 1141 | int 1142 | main(int argc, char **argv) 1143 | { 1144 | FD_SET(STDIN_FILENO, &fds); 1145 | setlocale(LC_ALL, ""); 1146 | signal(SIGCHLD, SIG_IGN); /* automatically reap children */ 1147 | 1148 | int c = 0; 1149 | while ((c = getopt(argc, argv, "c:T:t:")) != -1) switch (c){ 1150 | case 'c': commandkey = CTL(optarg[0]); break; 1151 | case 'T': setenv("TERM", optarg, 1); break; 1152 | case 't': term = optarg; break; 1153 | default: quit(EXIT_FAILURE, USAGE); break; 1154 | } 1155 | 1156 | if (!initscr()) 1157 | quit(EXIT_FAILURE, "could not initialize terminal"); 1158 | ESCDELAY = ESCAPE_TIME; 1159 | raw(); 1160 | noecho(); 1161 | nonl(); 1162 | intrflush(stdscr, FALSE); 1163 | start_color(); 1164 | use_default_colors(); 1165 | start_pairs(); 1166 | 1167 | root = newview(NULL, 0, 0, LINES, COLS); 1168 | if (!root) 1169 | quit(EXIT_FAILURE, "could not open root window"); 1170 | focus(root); 1171 | draw(root); 1172 | run(); 1173 | 1174 | quit(EXIT_SUCCESS, NULL); 1175 | return EXIT_SUCCESS; /* not reached */ 1176 | } 1177 | 1178 | -------------------------------------------------------------------------------- /mtm.ti: -------------------------------------------------------------------------------- 1 | mtm|Micro Terminal Multiplexer, 2 | am, bce, xenl, mir, msgr, cols#80, lines#25, it#8, colors#8, pairs#64, U8#1, 3 | acsc=++\,\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 4 | bel=\007, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[25l, 5 | clear=\E[H\E[J, cnorm=\E[25h, cr=\r, csr=\E[%i%p1%d;%p2%dr, 6 | cub1=\010, cub=\E[%p1%dD, cud1=\n, cud=\E[%p1%dB, cuf1=\E[C, 7 | cuf=\E[%p1%dC, cup=\E[%i%p1%d;%p2%dH, cuu1=\EM, cuu=\E[%p1%dA, 8 | cvvis=\E[34l, dch1=\E[P, dch=\E[%p1%dP, dim=\E[2m, dl1=\E[M, 9 | dl=\E[%p1%dM, ech=\E[%p1%dX, ed=\E[J, el1=\E[1K, el=\E[K, 10 | enacs=\E(B\E)0, home=\E[H, hpa=\E[%i%p1%dG, ht=\011, hts=\EH, 11 | ich=\E[%p1%d@, il1=\E[L, il=\E[%p1%dL, ind=\n, indn=\E[%p1%dS, is2=\E)0, 12 | kbs=\177, kcbt=\E[Z, kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA, 13 | kdch1=\E[3~, kend=\E[4~, kf1=\EOP, kf2=\EOQ, kf3=\EOR, kf4=\EOS, 14 | kf5=\E[15~, kf6=\E[17~, kf7=\E[18~, kf8=\E[19~, kf9=\E[20~, kf10=\E[21~, 15 | kf11=\E[23~, kf12=\E[24~, khome=\E[1~, kich1=\E[2~, knp=\E[6~, 16 | kpp=\E[5~, nel=\EE, op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, 17 | rin=\E[%p1%dT, rmacs=\017, rmcup=\E[1049l, rmir=\E[4l, rmkx=\E[1l\E>, 18 | sitm=\E[3m, rep=%p1%c\E[%p2%{1}%-%db, ritm=\E[23m, rmso=\E[27m, 19 | rmul=\E[24m, rs2=\Ec, sc=\E7, setab=\E[%p1%'('%+%dm, 20 | setaf=\E[%p1%{30}%+%dm, sgr0=\E[m\017, 21 | sgr=\E[0%?%p6%t;1%;%?%p1%t;3%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\016%e\017%;, 22 | smacs=\016, smcup=\E[1049h, smir=\E[4h, smkx=\E[1h\E=, smso=\E[7m, 23 | smul=\E[4m, tbc=\E[3g, vpa=\E[%i%p1%dd, E3=\E[3J, u8=\006, u9=\005, 24 | 25 | 26 | mtm-256color|Micro Terminal Multiplexer with 256 colors, 27 | colors#0x100, pairs#0x10000, 28 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 29 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 30 | use=mtm, 31 | 32 | mtm-noutf|Micro Terminal Multiplexer with no UTF8, 33 | @U8, 34 | use=mtm, 35 | -------------------------------------------------------------------------------- /pair.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.h" 4 | 5 | #define COLOR_MAX 256 6 | typedef struct PAIR PAIR; 7 | struct PAIR{ 8 | short fg, bg, cp; 9 | }; 10 | 11 | static PAIR pairs[COLOR_MAX * COLOR_MAX]; 12 | 13 | void 14 | start_pairs(void) 15 | { 16 | for (int i = 0; i < COLOR_MAX * COLOR_MAX; i++) 17 | pairs[i].fg = pairs[i].bg = pairs[i].cp = -1; 18 | } 19 | 20 | short 21 | mtm_alloc_pair(int fg, int bg) 22 | { 23 | #if USE_ALLOC_PAIR 24 | return alloc_pair(fg, bg); 25 | #else 26 | if (fg >= COLOR_MAX || bg >= COLOR_MAX) 27 | return -1; 28 | for (int i = 0; i < COLOR_MAX * COLOR_MAX; i++){ 29 | PAIR *p = pairs + i; 30 | if (p->cp == -1){ 31 | p->fg = fg; 32 | p->bg = bg; 33 | p->cp = init_pair(i + 1, p->fg, p->bg) == OK? i + 1 : -1; 34 | } 35 | if (p->fg == fg && p->bg == bg && p->cp != -1) 36 | return p->cp; 37 | } 38 | return -1; 39 | #endif 40 | } 41 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadpixi/mtm/b346b86208007762d8f6b006722e4f0113446820/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadpixi/mtm/b346b86208007762d8f6b006722e4f0113446820/screenshot2.png -------------------------------------------------------------------------------- /vtparser.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017-2019 Rob King 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * * Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * * Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * * Neither the name of the copyright holder nor the 12 | * names of contributors may be used to endorse or promote products 13 | * derived from this software without specific prior written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS, 19 | * COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 22 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #include 28 | #include 29 | #include "vtparser.h" 30 | 31 | /**** DATA TYPES */ 32 | #define MAXACTIONS 128 33 | 34 | typedef struct ACTION ACTION; 35 | struct ACTION{ 36 | wchar_t lo, hi; 37 | void (*cb)(VTPARSER *p, wchar_t w); 38 | STATE *next; 39 | }; 40 | 41 | struct STATE{ 42 | void (*entry)(VTPARSER *v); 43 | ACTION actions[MAXACTIONS]; 44 | }; 45 | 46 | /**** GLOBALS */ 47 | static STATE ground, escape, escape_intermediate, csi_entry, 48 | csi_ignore, csi_param, csi_intermediate, osc_string; 49 | 50 | /**** ACTION FUNCTIONS */ 51 | static void 52 | reset(VTPARSER *v) 53 | { 54 | v->inter = v->narg = v->nosc = 0; 55 | memset(v->args, 0, sizeof(v->args)); 56 | memset(v->oscbuf, 0, sizeof(v->oscbuf)); 57 | } 58 | 59 | static void 60 | ignore(VTPARSER *v, wchar_t w) 61 | { 62 | (void)v; (void)w; /* avoid warnings */ 63 | } 64 | 65 | static void 66 | collect(VTPARSER *v, wchar_t w) 67 | { 68 | v->inter = v->inter? v->inter : (int)w; 69 | } 70 | 71 | static void 72 | collectosc(VTPARSER *v, wchar_t w) 73 | { 74 | if (v->nosc < MAXOSC) 75 | v->oscbuf[v->nosc++] = w; 76 | } 77 | 78 | static void 79 | param(VTPARSER *v, wchar_t w) 80 | { 81 | v->narg = v->narg? v->narg : 1; 82 | 83 | if (w == L';') 84 | v->args[v->narg++] = 0; 85 | else if (v->narg < MAXPARAM && v->args[v->narg - 1] < 9999) 86 | v->args[v->narg - 1] = v->args[v->narg - 1] * 10 + (w - 0x30); 87 | } 88 | 89 | #define DO(k, t, f, n, a) \ 90 | static void \ 91 | do ## k (VTPARSER *v, wchar_t w) \ 92 | { \ 93 | if (t) \ 94 | f (v, v->p, w, v->inter, n, a, v->oscbuf); \ 95 | } 96 | 97 | DO(control, w < MAXCALLBACK && v->cons[w], v->cons[w], 0, NULL) 98 | DO(escape, w < MAXCALLBACK && v->escs[w], v->escs[w], v->inter > 0, &v->inter) 99 | DO(csi, w < MAXCALLBACK && v->csis[w], v->csis[w], v->narg, v->args) 100 | DO(print, v->print, v->print, 0, NULL) 101 | DO(osc, v->osc, v->osc, v->nosc, NULL) 102 | 103 | /**** PUBLIC FUNCTIONS */ 104 | VTCALLBACK 105 | vtonevent(VTPARSER *vp, VtEvent t, wchar_t w, VTCALLBACK cb) 106 | { 107 | VTCALLBACK o = NULL; 108 | if (w < MAXCALLBACK) switch (t){ 109 | case VTPARSER_CONTROL: o = vp->cons[w]; vp->cons[w] = cb; break; 110 | case VTPARSER_ESCAPE: o = vp->escs[w]; vp->escs[w] = cb; break; 111 | case VTPARSER_CSI: o = vp->csis[w]; vp->csis[w] = cb; break; 112 | case VTPARSER_PRINT: o = vp->print; vp->print = cb; break; 113 | case VTPARSER_OSC: o = vp->osc; vp->osc = cb; break; 114 | } 115 | 116 | return o; 117 | } 118 | 119 | static void 120 | handlechar(VTPARSER *vp, wchar_t w) 121 | { 122 | vp->s = vp->s? vp->s : &ground; 123 | for (ACTION *a = vp->s->actions; a->cb; a++) if (w >= a->lo && w <= a->hi){ 124 | a->cb(vp, w); 125 | if (a->next){ 126 | vp->s = a->next; 127 | if (a->next->entry) 128 | a->next->entry(vp); 129 | } 130 | return; 131 | } 132 | } 133 | 134 | void 135 | vtwrite(VTPARSER *vp, const char *s, size_t n) 136 | { 137 | wchar_t w = 0; 138 | while (n){ 139 | size_t r = mbrtowc(&w, s, n, &vp->ms); 140 | switch (r){ 141 | case -2: /* incomplete character, try again */ 142 | return; 143 | 144 | case -1: /* invalid character, skip it */ 145 | w = VTPARSER_BAD_CHAR; 146 | r = 1; 147 | break; 148 | 149 | case 0: /* literal zero, write it but advance */ 150 | r = 1; 151 | break; 152 | } 153 | 154 | n -= r; 155 | s += r; 156 | handlechar(vp, w); 157 | } 158 | } 159 | 160 | /**** STATE DEFINITIONS 161 | * This was built by consulting the excellent state chart created by 162 | * Paul Flo Williams: http://vt100.net/emu/dec_ansi_parser 163 | * Please note that Williams does not (AFAIK) endorse this work. 164 | */ 165 | #define MAKESTATE(name, onentry, ...) \ 166 | static STATE name ={ \ 167 | onentry , \ 168 | { \ 169 | {0x00, 0x00, ignore, NULL}, \ 170 | {0x7f, 0x7f, ignore, NULL}, \ 171 | {0x18, 0x18, docontrol, &ground}, \ 172 | {0x1a, 0x1a, docontrol, &ground}, \ 173 | {0x1b, 0x1b, ignore, &escape}, \ 174 | {0x01, 0x06, docontrol, NULL}, \ 175 | {0x08, 0x17, docontrol, NULL}, \ 176 | {0x19, 0x19, docontrol, NULL}, \ 177 | {0x1c, 0x1f, docontrol, NULL}, \ 178 | __VA_ARGS__ , \ 179 | {0x07, 0x07, docontrol, NULL}, \ 180 | {0x00, 0x00, NULL, NULL} \ 181 | } \ 182 | } 183 | 184 | MAKESTATE(ground, NULL, 185 | {0x20, WCHAR_MAX, doprint, NULL} 186 | ); 187 | 188 | MAKESTATE(escape, reset, 189 | {0x21, 0x21, ignore, &osc_string}, 190 | {0x20, 0x2f, collect, &escape_intermediate}, 191 | {0x30, 0x4f, doescape, &ground}, 192 | {0x51, 0x57, doescape, &ground}, 193 | {0x59, 0x59, doescape, &ground}, 194 | {0x5a, 0x5a, doescape, &ground}, 195 | {0x5c, 0x5c, doescape, &ground}, 196 | {0x6b, 0x6b, ignore, &osc_string}, 197 | {0x60, 0x7e, doescape, &ground}, 198 | {0x5b, 0x5b, ignore, &csi_entry}, 199 | {0x5d, 0x5d, ignore, &osc_string}, 200 | {0x5e, 0x5e, ignore, &osc_string}, 201 | {0x50, 0x50, ignore, &osc_string}, 202 | {0x5f, 0x5f, ignore, &osc_string} 203 | ); 204 | 205 | MAKESTATE(escape_intermediate, NULL, 206 | {0x20, 0x2f, collect, NULL}, 207 | {0x30, 0x7e, doescape, &ground} 208 | ); 209 | 210 | MAKESTATE(csi_entry, reset, 211 | {0x20, 0x2f, collect, &csi_intermediate}, 212 | {0x3a, 0x3a, ignore, &csi_ignore}, 213 | {0x30, 0x39, param, &csi_param}, 214 | {0x3b, 0x3b, param, &csi_param}, 215 | {0x3c, 0x3f, collect, &csi_param}, 216 | {0x40, 0x7e, docsi, &ground} 217 | ); 218 | 219 | MAKESTATE(csi_ignore, NULL, 220 | {0x20, 0x3f, ignore, NULL}, 221 | {0x40, 0x7e, ignore, &ground} 222 | ); 223 | 224 | MAKESTATE(csi_param, NULL, 225 | {0x30, 0x39, param, NULL}, 226 | {0x3b, 0x3b, param, NULL}, 227 | {0x3a, 0x3a, ignore, &csi_ignore}, 228 | {0x3c, 0x3f, ignore, &csi_ignore}, 229 | {0x20, 0x2f, collect, &csi_intermediate}, 230 | {0x40, 0x7e, docsi, &ground} 231 | ); 232 | 233 | MAKESTATE(csi_intermediate, NULL, 234 | {0x20, 0x2f, collect, NULL}, 235 | {0x30, 0x3f, ignore, &csi_ignore}, 236 | {0x40, 0x7e, docsi, &ground} 237 | ); 238 | 239 | MAKESTATE(osc_string, reset, 240 | {0x07, 0x07, doosc, &ground}, 241 | {0x20, 0x7f, collectosc, NULL} 242 | ); 243 | -------------------------------------------------------------------------------- /vtparser.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017-2019 Rob King 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * * Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * * Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * * Neither the name of the copyright holder nor the 12 | * names of contributors may be used to endorse or promote products 13 | * derived from this software without specific prior written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS, 19 | * COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 22 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #ifndef VTC_H 28 | #define VTC_H 29 | 30 | #include 31 | #include 32 | 33 | /**** CONFIGURATION 34 | * VTPARSER_BAD_CHAR is the character that will be displayed when 35 | * an application sends an invalid multibyte sequence to the terminal. 36 | */ 37 | #ifndef VTPARSER_BAD_CHAR 38 | #ifdef __STDC_ISO_10646__ 39 | #define VTPARSER_BAD_CHAR ((wchar_t)0xfffd) 40 | #else 41 | #define VTPARSER_BAD_CHAR L'?' 42 | #endif 43 | #endif 44 | 45 | /**** DATA TYPES */ 46 | #define MAXPARAM 16 47 | #define MAXCALLBACK 128 48 | #define MAXOSC 100 49 | #define MAXBUF 100 50 | 51 | typedef struct VTPARSER VTPARSER; 52 | typedef struct STATE STATE; 53 | typedef void (*VTCALLBACK)(VTPARSER *v, void *p, wchar_t w, wchar_t iw, 54 | int argc, int *argv, const wchar_t *osc); 55 | 56 | struct VTPARSER{ 57 | STATE *s; 58 | int narg, nosc, args[MAXPARAM], inter, oscbuf[MAXOSC + 1]; 59 | mbstate_t ms; 60 | void *p; 61 | VTCALLBACK print, osc, cons[MAXCALLBACK], escs[MAXCALLBACK], 62 | csis[MAXCALLBACK]; 63 | }; 64 | 65 | typedef enum{ 66 | VTPARSER_CONTROL, 67 | VTPARSER_ESCAPE, 68 | VTPARSER_CSI, 69 | VTPARSER_OSC, 70 | VTPARSER_PRINT 71 | } VtEvent; 72 | 73 | /**** FUNCTIONS */ 74 | VTCALLBACK 75 | vtonevent(VTPARSER *vp, VtEvent t, wchar_t w, VTCALLBACK cb); 76 | 77 | void 78 | vtwrite(VTPARSER *vp, const char *s, size_t n); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /vttest1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadpixi/mtm/b346b86208007762d8f6b006722e4f0113446820/vttest1.png -------------------------------------------------------------------------------- /vttest2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadpixi/mtm/b346b86208007762d8f6b006722e4f0113446820/vttest2.png --------------------------------------------------------------------------------