├── FAQ ├── LEGACY ├── LICENSE ├── Makefile ├── README ├── TODO ├── arg.h ├── config.def.h ├── config.mk ├── st.1 ├── st.c └── st.info /FAQ: -------------------------------------------------------------------------------- 1 | ## Why does st not handle utmp entries? 2 | 3 | Use the excellent tool of [utmp](http://git.suckless.org/utmp/) for this task. 4 | 5 | ## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! 6 | 7 | It means that st doesn’t have any terminfo entry on your system. Chances are 8 | you did not `make install`. If you just want to test it without installing it, 9 | you can manualy run `tic -s st.info`. 10 | 11 | ## Nothing works, and nothing is said about an unknown terminal! 12 | 13 | * Some programs just assume they’re running in xterm i.e. they don’t rely on 14 | terminfo. What you see is the current state of the “xterm compliance”. 15 | * Some programs don’t complain about the lacking st description and default to 16 | another terminal. In that case see the question about terminfo. 17 | 18 | ## I get some weird glitches/visual bug on _random program_! 19 | 20 | Try launching it with a different TERM: $ TERM=xterm myapp. toe(1) will give 21 | you a list of available terminals, but you’ll most likely switch between xterm, 22 | st or st-256color. The default value for TERM can be changed in config.h 23 | (TNAME). 24 | 25 | ## How do I scroll back up? 26 | 27 | Using a terminal multiplexer. 28 | 29 | * `st -e tmux` using C-a [ 30 | * `st -e screen` using C-a ESC 31 | 32 | ## Why doesn't the Del key work in some programs? 33 | 34 | Taken from the terminfo manpage: 35 | 36 | If the terminal has a keypad that transmits codes when the keys 37 | are pressed, this information can be given. Note that it is not 38 | possible to handle terminals where the keypad only works in 39 | local (this applies, for example, to the unshifted HP 2621 keys). 40 | If the keypad can be set to transmit or not transmit, tive these 41 | codes as smkx and rmkx. Otherwise the keypad is assumed to 42 | always transmit. 43 | 44 | In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that 45 | applications which want to test against keypad keys, have to send these 46 | sequences. 47 | 48 | But buggy applications like bash and irssi for example don't do this. A fast 49 | solution for them is to use the following command: 50 | 51 | $ printf "\033?1h\033=" >/dev/tty 52 | 53 | or 54 | $ echo $(tput smkx) >/dev/tty 55 | 56 | In the case of bash readline is used. Readline has a different note in its 57 | manpage about this issue: 58 | 59 | enable-keypad (Off) 60 | When set to On, readline will try to enable the 61 | application keypad when it is called. Some systems 62 | need this to enable arrow keys. 63 | 64 | Adding this option to your .inputrc will fix the keypad problem for all 65 | applications using readline. 66 | 67 | If you are using zsh, then read the zsh FAQ 68 | : 69 | 70 | It should be noted that the O / [ confusion can occur with other keys 71 | such as Home and End. Some systems let you query the key sequences 72 | sent by these keys from the system's terminal database, terminfo. 73 | Unfortunately, the key sequences given there typically apply to the 74 | mode that is not the one zsh uses by default (it's the "application" 75 | mode rather than the "raw" mode). Explaining the use of terminfo is 76 | outside of the scope of this FAQ, but if you wish to use the key 77 | sequences given there you can tell the line editor to turn on 78 | "application" mode when it starts and turn it off when it stops: 79 | 80 | function zle-line-init () { echoti smkx } 81 | function zle-line-finish () { echoti rmkx } 82 | zle -N zle-line-init 83 | zle -N zle-line-finish 84 | 85 | Putting these lines into your .zshrc will fix the problems. 86 | -------------------------------------------------------------------------------- /LEGACY: -------------------------------------------------------------------------------- 1 | A STATEMENT ON LEGACY SUPPORT 2 | 3 | In the terminal world there is much cruft that comes from old and unsup‐ 4 | ported terminals that inherit incompatible modes and escape sequences 5 | which noone is able to know, except when he/she comes from that time and 6 | developed a graphical vt100 emulator at that time. 7 | 8 | One goal of st is to only support what is really needed. When you en‐ 9 | counter a sequence which you really need, implement it. But while you 10 | are at it, do not add the other cruft you might encounter while sneek‐ 11 | ing at other terminal emulators. History has bloated them and there is 12 | no real evidence that most of the sequences are used today. 13 | 14 | 15 | Christoph Lohmann <20h@r-36.net> 16 | 2012-09-13T07:00:36.081271045+02:00 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2009-2012 Aurélien APTEL 4 | © 2012 Roberto E. Vargas Caballero 5 | © 2012 Christoph Lohmann <20h at r-36 dot net> 6 | © 2009 Anselm R Garbe 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # st - simple terminal 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = st.c 7 | OBJ = ${SRC:.c=.o} 8 | 9 | all: options st 10 | 11 | options: 12 | @echo st build options: 13 | @echo "CFLAGS = ${CFLAGS}" 14 | @echo "LDFLAGS = ${LDFLAGS}" 15 | @echo "CC = ${CC}" 16 | 17 | config.h: 18 | cp config.def.h config.h 19 | 20 | .c.o: 21 | @echo CC $< 22 | @${CC} -c ${CFLAGS} $< 23 | 24 | ${OBJ}: config.h config.mk 25 | 26 | st: ${OBJ} 27 | @echo CC -o $@ 28 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 29 | 30 | clean: 31 | @echo cleaning 32 | @rm -f st ${OBJ} st-${VERSION}.tar.gz 33 | 34 | dist: clean 35 | @echo creating dist tarball 36 | @mkdir -p st-${VERSION} 37 | @cp -R LICENSE Makefile README config.mk config.def.h st.info st.1 ${SRC} st-${VERSION} 38 | @tar -cf st-${VERSION}.tar st-${VERSION} 39 | @gzip st-${VERSION}.tar 40 | @rm -rf st-${VERSION} 41 | 42 | install: all 43 | @echo installing executable file to ${DESTDIR}${PREFIX}/bin 44 | @mkdir -p ${DESTDIR}${PREFIX}/bin 45 | @cp -f st ${DESTDIR}${PREFIX}/bin 46 | @chmod 755 ${DESTDIR}${PREFIX}/bin/st 47 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 48 | @mkdir -p ${DESTDIR}${MANPREFIX}/man1 49 | @sed "s/VERSION/${VERSION}/g" < st.1 > ${DESTDIR}${MANPREFIX}/man1/st.1 50 | @chmod 644 ${DESTDIR}${MANPREFIX}/man1/st.1 51 | @echo Please see the README file regarding the terminfo entry of st. 52 | @tic -s st.info 53 | 54 | uninstall: 55 | @echo removing executable file from ${DESTDIR}${PREFIX}/bin 56 | @rm -f ${DESTDIR}${PREFIX}/bin/st 57 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 58 | @rm -f ${DESTDIR}${MANPREFIX}/man1/st.1 59 | 60 | .PHONY: all options clean dist install uninstall 61 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | st - simple terminal 2 | -------------------- 3 | st is a simple virtual terminal emulator for X which sucks less. 4 | 5 | chjj's fork 6 | ----------- 7 | My fork includes features like tmux-like tabs, select/visual/search mode, and 8 | scrollback. 9 | 10 | Requirements 11 | ------------ 12 | In order to build st you need the Xlib header files. 13 | 14 | 15 | Installation 16 | ------------ 17 | Edit config.mk to match your local setup (st is installed into 18 | the /usr/local namespace by default). 19 | 20 | Afterwards enter the following command to build and install st (if 21 | necessary as root): 22 | 23 | make clean install 24 | 25 | 26 | Running st 27 | ---------- 28 | If you did not install st with make clean install, you must compile 29 | the st terminfo entry with the following command: 30 | 31 | tic -s st.info 32 | 33 | See the man page for additional details. 34 | 35 | Credits 36 | ------- 37 | Based on Aurélien APTEL bt source code. 38 | 39 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | vt emulation 2 | ------------ 3 | 4 | * wide-character support in conjunction with fallback xft code 5 | * double-height support 6 | 7 | code & interface 8 | ---------------- 9 | 10 | * clean and complete terminfo entry 11 | * add a simple way to do multiplexing 12 | 13 | drawing 14 | ------- 15 | * add diacritics support to xdraws() 16 | * add kerning configuration 17 | * make the font cache simpler 18 | * add hard width handling 19 | * xft is reporting wrong width and height for characters 20 | 21 | bugs 22 | ---- 23 | 24 | * fix shift up/down (shift selection in emacs) 25 | * fix selection paste for xatom STRING 26 | * fix rows and column definition in fixed geometry 27 | * fix -e handling 28 | * remove DEC test sequence when appropriate 29 | * When some application outputting long text is run in the shell init scripts, 30 | then this text might be stripped to the standard 80x25 due to st running the 31 | virtual terminal at first priority. Maybe the vt initialisation could be 32 | moved somewhere after knowing the right window size. 33 | 34 | misc 35 | ---- 36 | 37 | $ grep -nE 'XXX|TODO' st.c 38 | 39 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h 4 | */ 5 | 6 | #ifndef __ARG_H__ 7 | #define __ARG_H__ 8 | 9 | extern char *argv0; 10 | 11 | #define USED(x) ((void)(x)) 12 | 13 | /* use main(int argc, char *argv[]) */ 14 | #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ 15 | argv[0] && argv[0][1]\ 16 | && argv[0][0] == '-';\ 17 | argc--, argv++) {\ 18 | char _argc;\ 19 | char **_argv;\ 20 | int brk;\ 21 | if (argv[0][1] == '-' && argv[0][2] == '\0') {\ 22 | argv++;\ 23 | argc--;\ 24 | break;\ 25 | }\ 26 | for (brk = 0, argv[0]++, _argv = argv;\ 27 | argv[0][0] && !brk;\ 28 | argv[0]++) {\ 29 | if (_argv != argv)\ 30 | break;\ 31 | _argc = argv[0][0];\ 32 | switch (_argc) 33 | 34 | #define ARGEND }\ 35 | USED(_argc);\ 36 | }\ 37 | USED(argv);\ 38 | USED(argc); 39 | 40 | #define ARGC() _argc 41 | 42 | #define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ 43 | ((x), abort(), (char *)0) :\ 44 | (brk = 1, (argv[0][1] != '\0')?\ 45 | (&argv[0][1]) :\ 46 | (argc--, argv++, argv[0]))) 47 | 48 | #define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ 49 | (char *)0 :\ 50 | (brk = 1, (argv[0][1] != '\0')?\ 51 | (&argv[0][1]) :\ 52 | (argc--, argv++, argv[0]))) 53 | 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | /* 4 | * appearance 5 | * 6 | * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html 7 | */ 8 | #if 0 9 | static char font[] = "Liberation Mono:pixelsize=12:antialias=false:autohint=false"; 10 | #endif 11 | // static char font[] = "Deja Vu Sans Mono:pixelsize=11:antialias=true"; 12 | static char font[] = "-*-terminus-medium-*-*-*-12-*-*-*-*-*-*-*"; 13 | 14 | static int borderpx = 2; 15 | static char shell[] = "/bin/sh"; 16 | 17 | /* 18 | * word delimiter string 19 | * 20 | * More advanced example: " `'\"()[]{}" 21 | */ 22 | static char worddelimiters[] = " "; 23 | 24 | /* selection timeouts (in milliseconds) */ 25 | static unsigned int doubleclicktimeout = 300; 26 | static unsigned int tripleclicktimeout = 600; 27 | 28 | /* scrollback */ 29 | static int scrollback = 10000; 30 | 31 | /* activity markers */ 32 | static bool showactivity = false; 33 | 34 | /* autohide statusbar */ 35 | static bool autohide = true; 36 | 37 | /* always show tab number */ 38 | static bool alwaysshownumber = true; 39 | 40 | /* no text cursor */ 41 | static bool notextcursor = true; 42 | 43 | /* show new tab button */ 44 | static bool shownewbutton = false; 45 | 46 | /* alt screens */ 47 | static bool allowaltscreen = true; 48 | 49 | /* frames per second st should at maximum draw to the screen */ 50 | static unsigned int xfps = 60; 51 | static unsigned int actionfps = 30; 52 | 53 | /* 54 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 55 | * attribute. 56 | */ 57 | static unsigned int blinktimeout = 800; 58 | 59 | /* TERM value */ 60 | #if 0 61 | static char termname[] = "st-256color"; 62 | #endif 63 | static char termname[] = "xterm-256color"; 64 | 65 | static unsigned int tabspaces = 8; 66 | 67 | #if 0 68 | /* Terminal colors (16 first used in escape sequence) */ 69 | static const char *colorname[] = { 70 | /* 8 normal colors */ 71 | "black", 72 | "red3", 73 | "green3", 74 | "yellow3", 75 | "blue2", 76 | "magenta3", 77 | "cyan3", 78 | "gray90", 79 | 80 | /* 8 bright colors */ 81 | "gray50", 82 | "red", 83 | "green", 84 | "yellow", 85 | "#5c5cff", 86 | "magenta", 87 | "cyan", 88 | "white", 89 | 90 | [255] = 0, 91 | 92 | /* more colors can be added after 255 to use with DefaultXX */ 93 | "#cccccc", 94 | }; 95 | #endif 96 | 97 | /* Terminal colors (16 first used in escape sequence) */ 98 | static const char *colorname[] = { 99 | /* 8 normal colors */ 100 | // black 101 | "#2e3436", 102 | // red 103 | "#cc0000", 104 | // green 105 | "#4e9a06", 106 | // yellow 107 | "#c4a000", 108 | // blue 109 | "#3465a4", 110 | // magenta 111 | "#75507b", 112 | // cyan 113 | "#06989a", 114 | // white 115 | "#d3d7cf", 116 | 117 | /* 8 bright colors */ 118 | // black 119 | "#555753", 120 | // red 121 | "#ef2929", 122 | // green 123 | "#8ae234", 124 | // yellow 125 | "#fce94f", 126 | // blue 127 | "#729fcf", 128 | // magenta 129 | "#ad7fa8", 130 | // cyan 131 | "#34e2e2", 132 | // white 133 | "#eeeeec", 134 | 135 | [255] = 0, 136 | 137 | /* more colors can be added after 255 to use with DefaultXX */ 138 | /* fg / cursor */ 139 | "#eeeeee", 140 | /* bg */ 141 | "#111111", 142 | }; 143 | 144 | /* 145 | * Default colors (colorname index) 146 | * foreground, background, cursor 147 | */ 148 | #if 0 149 | static unsigned int defaultfg = 7; 150 | static unsigned int defaultbg = 0; 151 | static unsigned int defaultcs = 256; 152 | #endif 153 | 154 | static unsigned int defaultfg = 256; 155 | static unsigned int defaultbg = 257; 156 | static unsigned int defaultcs = 256; 157 | 158 | /* similar to my tmux setup */ 159 | #if 0 160 | static unsigned int defaultbarbg = 257; 161 | static unsigned int defaultbarfg = 256; 162 | 163 | static unsigned int selbarbg = 257; 164 | static unsigned int selbarfg = 15; 165 | 166 | static unsigned int unselbarbg = 257; 167 | static unsigned int unselbarfg = 6; 168 | #endif 169 | 170 | /* a bar with an actual background */ 171 | #if 0 172 | /* Set 258 to "#202020" above. */ 173 | //static unsigned int defaultbarbg = 258; 174 | static unsigned int defaultbarbg = 0; 175 | static unsigned int defaultbarfg = 256; 176 | 177 | static unsigned int selbarbg = 8; 178 | static unsigned int selbarfg = 256; 179 | 180 | //static unsigned int unselbarbg = 258; 181 | static unsigned int unselbarbg = 0; 182 | static unsigned int unselbarfg = 256; 183 | #endif 184 | 185 | /* dark black/white */ 186 | static unsigned int defaultbarbg = 257; 187 | static unsigned int defaultbarfg = 15; 188 | 189 | static unsigned int selbarbg = 257; 190 | static unsigned int selbarfg = 15; 191 | 192 | static unsigned int unselbarbg = 257; 193 | static unsigned int unselbarfg = 8; 194 | 195 | /* 196 | * Colors used, when the specific fg == defaultfg. So in reverse mode this 197 | * will reverse too. Another logic would only make the simple feature too 198 | * complex. 199 | */ 200 | static unsigned int defaultitalic = 11; 201 | static unsigned int defaultunderline = 7; 202 | 203 | /* Internal mouse shortcuts. */ 204 | /* Beware that overloading Button1 will disable the selection. */ 205 | static Mousekey mshortcuts[] = { 206 | /* keysym mask string */ 207 | #if 0 208 | { Button4, XK_ANY_MOD, "\031"}, 209 | { Button5, XK_ANY_MOD, "\005"}, 210 | #endif 211 | {0} 212 | }; 213 | 214 | /* Internal keyboard shortcuts. */ 215 | #define MODKEY Mod1Mask 216 | 217 | static Shortcut shortcuts[] = { 218 | /* modifier key function argument */ 219 | { MODKEY|ShiftMask, XK_Prior, xzoom, {.i = +1} }, 220 | { MODKEY|ShiftMask, XK_Next, xzoom, {.i = -1} }, 221 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 222 | { MODKEY|ShiftMask, XK_Insert, clippaste, {.i = 0} }, 223 | { MODKEY, XK_Num_Lock, numlock, {.i = 0} }, 224 | }; 225 | 226 | /* 227 | * Special keys (change & recompile st.info accordingly) 228 | * 229 | * Mask value: 230 | * * Use XK_ANY_MOD to match the key no matter modifiers state 231 | * * Use XK_NO_MOD to match the key alone (no modifiers) 232 | * keypad value: 233 | * * 0: no value 234 | * * > 0: keypad application mode enabled 235 | * * = 2: term.numlock = 1 236 | * * < 0: keypad application mode disabled 237 | * cursor value: 238 | * * 0: no value 239 | * * > 0: cursor application mode enabled 240 | * * < 0: cursor application mode disabled 241 | * crlf value 242 | * * 0: no value 243 | * * > 0: crlf mode is enabled 244 | * * < 0: crlf mode is disabled 245 | * 246 | * Be careful with the order of the definitons because st searchs in 247 | * this table sequencially, so any XK_ANY_MOD must be in the last 248 | * position for a key. 249 | */ 250 | 251 | /* 252 | * If you want something else but the function keys of X11 (0xFF00 - 0xFFFF) 253 | * mapped below, add them to this array. 254 | */ 255 | static KeySym mappedkeys[] = { -1 }; 256 | 257 | /* 258 | * Which bits of the state should be ignored. By default the state bit for the 259 | * keyboard layout (XK_SWITCH_MOD) is ignored. 260 | */ 261 | uint ignoremod = XK_SWITCH_MOD; 262 | 263 | /* key, mask, output, keypad, cursor, crlf */ 264 | static Key key[] = { 265 | /* keysym mask string keypad cursor crlf */ 266 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, 0, 0}, 267 | { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, 268 | { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, 269 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0, 0}, 270 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, 271 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, 272 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0, 0}, 273 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, 274 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, 275 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0, 0}, 276 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, 277 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, 278 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0, 0}, 279 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, 280 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, 281 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, 282 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, 283 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0, 0}, 284 | { XK_KP_End, ControlMask, "\033[J", -1, 0, 0}, 285 | { XK_KP_End, ControlMask, "\033[1;5F", +1, 0, 0}, 286 | { XK_KP_End, ShiftMask, "\033[K", -1, 0, 0}, 287 | { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0, 0}, 288 | { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, 289 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, 290 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, 291 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, 292 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0, 0}, 293 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0, 0}, 294 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, 295 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, 296 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, 297 | { XK_KP_Delete, ControlMask, "\033[2J", -1, 0, 0}, 298 | { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, 299 | { XK_KP_Delete, ShiftMask, "\033[2K", +1, 0, 0}, 300 | { XK_KP_Delete, ShiftMask, "\033[3;2~", -1, 0, 0}, 301 | { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, 302 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, 303 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0, 0}, 304 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0, 0}, 305 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0, 0}, 306 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0, -1}, 307 | { XK_KP_Enter, XK_ANY_MOD, "\r\n", -1, 0, +1}, 308 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0, 0}, 309 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0, 0}, 310 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0, 0}, 311 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0, 0}, 312 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0, 0}, 313 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0, 0}, 314 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0, 0}, 315 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0, 0}, 316 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0, 0}, 317 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0, 0}, 318 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0, 0}, 319 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0, 0}, 320 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0, 0}, 321 | { XK_BackSpace, XK_NO_MOD, "\177", 0, 0, 0}, 322 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0, 0}, 323 | { XK_Up, ControlMask, "\033[1;5A", 0, 0, 0}, 324 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0, 0}, 325 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, 326 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, 327 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0, 0}, 328 | { XK_Down, ControlMask, "\033[1;5B", 0, 0, 0}, 329 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0, 0}, 330 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, 331 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, 332 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0, 0}, 333 | { XK_Left, ControlMask, "\033[1;5D", 0, 0, 0}, 334 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0, 0}, 335 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, 336 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, 337 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0, 0}, 338 | { XK_Right, ControlMask, "\033[1;5C", 0, 0, 0}, 339 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0, 0}, 340 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, 341 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, 342 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0, 0}, 343 | { XK_Return, Mod1Mask, "\033\r", 0, 0, -1}, 344 | { XK_Return, Mod1Mask, "\033\r\n", 0, 0, +1}, 345 | { XK_Return, XK_ANY_MOD, "\r", 0, 0, -1}, 346 | { XK_Return, XK_ANY_MOD, "\r\n", 0, 0, +1}, 347 | { XK_Insert, ShiftMask, "\033[4l", -1, 0, 0}, 348 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, 349 | { XK_Insert, ControlMask, "\033[L", -1, 0, 0}, 350 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, 351 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, 352 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, 353 | { XK_Delete, ControlMask, "\033[2J", -1, 0, 0}, 354 | { XK_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, 355 | { XK_Delete, ShiftMask, "\033[2K", +1, 0, 0}, 356 | { XK_Delete, ShiftMask, "\033[3;2~", -1, 0, 0}, 357 | { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, 358 | { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, 359 | { XK_Home, ShiftMask, "\033[1;2H", 0, 0, 0}, 360 | { XK_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, 361 | { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, 362 | { XK_End, ControlMask, "\033[J", -1, 0, 0}, 363 | { XK_End, ControlMask, "\033[1;5F", +1, 0, 0}, 364 | { XK_End, ShiftMask, "\033[K", -1, 0, 0}, 365 | { XK_End, ShiftMask, "\033[1;2F", +1, 0, 0}, 366 | { XK_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, 367 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0, 0}, 368 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, 369 | { XK_Prior, XK_NO_MOD, "\033[5~", 0, 0, 0}, 370 | { XK_Next, ControlMask, "\033[6;5~", 0, 0, 0}, 371 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, 372 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, 373 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0, 0}, 374 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0, 0}, 375 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0, 0}, 376 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0, 0}, 377 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0, 0}, 378 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0, 0}, 379 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0, 0}, 380 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0, 0}, 381 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0, 0}, 382 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0, 0}, 383 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0, 0}, 384 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0, 0}, 385 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0, 0}, 386 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0, 0}, 387 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0, 0}, 388 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0, 0}, 389 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0, 0}, 390 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0, 0}, 391 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0, 0}, 392 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0, 0}, 393 | { XK_F4, /* F28 */ ShiftMask, "\033[1;5S", 0, 0, 0}, 394 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0, 0}, 395 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0, 0}, 396 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0, 0}, 397 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0, 0}, 398 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0, 0}, 399 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0, 0}, 400 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0, 0}, 401 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0, 0}, 402 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0, 0}, 403 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0, 0}, 404 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0, 0}, 405 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0, 0}, 406 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0, 0}, 407 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0, 0}, 408 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0, 0}, 409 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0, 0}, 410 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0, 0}, 411 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0, 0}, 412 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0, 0}, 413 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0, 0}, 414 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0, 0}, 415 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0, 0}, 416 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0, 0}, 417 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0, 0}, 418 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0, 0}, 419 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0, 0}, 420 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0, 0}, 421 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0, 0}, 422 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0, 0}, 423 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0, 0}, 424 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0, 0}, 425 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0, 0}, 426 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0, 0}, 427 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0, 0}, 428 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0, 0}, 429 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0, 0}, 430 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0, 0}, 431 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0, 0}, 432 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0, 0}, 433 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0, 0}, 434 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0, 0}, 435 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0, 0}, 436 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0, 0}, 437 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0, 0}, 438 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0, 0}, 439 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0, 0}, 440 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0, 0}, 441 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0, 0}, 442 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0, 0}, 443 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0, 0}, 444 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0, 0}, 445 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0, 0}, 446 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0, 0}, 447 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0, 0}, 448 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0, 0}, 449 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0, 0}, 450 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0, 0}, 451 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0, 0}, 452 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0, 0}, 453 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0, 0}, 454 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0, 0}, 455 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0, 0}, 456 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0, 0}, 457 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0, 0}, 458 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0, 0}, 459 | }; 460 | 461 | /* 462 | * Selection types' masks. 463 | * Use the same masks as usual. 464 | * Button1Mask is always unset, to make masks match between ButtonPress. 465 | * ButtonRelease and MotionNotify. 466 | * If no match is found, regular selection is used. 467 | */ 468 | 469 | static uint selmasks[] = { 470 | [SEL_RECTANGULAR] = Mod1Mask, 471 | }; 472 | 473 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # st version 2 | VERSION = 0.4.1 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX = /usr/local 8 | MANPREFIX = ${PREFIX}/share/man 9 | 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | # includes and libs 14 | INCS = -I. -I/usr/include -I${X11INC} \ 15 | `pkg-config --cflags fontconfig` \ 16 | `pkg-config --cflags freetype2` 17 | LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lutil -lXext -lXft \ 18 | `pkg-config --libs fontconfig` \ 19 | `pkg-config --libs freetype2` 20 | 21 | # flags 22 | CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_XOPEN_SOURCE=600 23 | CFLAGS += -g -std=c99 -pedantic -Wall -Wvariadic-macros -Os ${INCS} ${CPPFLAGS} 24 | LDFLAGS += -g ${LIBS} 25 | 26 | # compiler and linker 27 | CC ?= cc 28 | 29 | -------------------------------------------------------------------------------- /st.1: -------------------------------------------------------------------------------- 1 | .TH ST 1 st\-VERSION 2 | .SH NAME 3 | st \- simple terminal 4 | .SH SYNOPSIS 5 | .B st 6 | .RB [ \-a ] 7 | .RB [ \-c 8 | .IR class ] 9 | .RB [ \-f 10 | .IR font ] 11 | .RB [ \-g 12 | .IR geometry ] 13 | .RB [ \-o 14 | .IR file ] 15 | .RB [ \-t 16 | .IR title ] 17 | .RB [ \-w 18 | .IR windowid ] 19 | .RB [ \-v ] 20 | .RB [ \-e 21 | .IR command ...] 22 | .SH DESCRIPTION 23 | .B st 24 | is a simple terminal emulator. 25 | .SH OPTIONS 26 | .TP 27 | .B \-a 28 | disable alternate screens in terminal 29 | .TP 30 | .BI \-c " class" 31 | defines the window class (default $TERM). 32 | .TP 33 | .BI \-f " font" 34 | defines the 35 | .I font 36 | to use when st is run. 37 | .TP 38 | .BI \-g " geometry" 39 | defines the X11 geometry string, which will fixate the height and width of st. 40 | The form is [=][{xX}][{+-}{+-}]. See 41 | .BR XParseGeometry (3) 42 | for further details. 43 | .TP 44 | .BI \-o " file" 45 | writes all the I/O to 46 | .I file. 47 | This feature is useful when recording st sessions. A value of "-" means 48 | standard output. 49 | .TP 50 | .BI \-t " title" 51 | defines the window title (default 'st'). 52 | .TP 53 | .BI \-w " windowid" 54 | embeds st within the window identified by 55 | .I windowid 56 | .TP 57 | .B \-v 58 | prints version information to stderr, then exits. 59 | .TP 60 | .BI \-e " program " [ " arguments " "... ]" 61 | st executes 62 | .I program 63 | instead of the shell. If this is used it 64 | .B must be the last option 65 | on the command line, as in xterm / rxvt. 66 | .SH CUSTOMIZATION 67 | .B st 68 | can be customized by creating a custom config.h and (re)compiling the source 69 | code. This keeps it fast, secure and simple. 70 | .SH AUTHORS 71 | See the LICENSE file for the authors. 72 | .SH LICENSE 73 | See the LICENSE file for the terms of redistribution. 74 | .SH SEE ALSO 75 | .BR tabbed (1) 76 | .SH BUGS 77 | See the TODO file in the distribution. 78 | 79 | -------------------------------------------------------------------------------- /st.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 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 | 30 | #include "arg.h" 31 | 32 | char *argv0; 33 | 34 | #define Glyph Glyph_ 35 | #define Font Font_ 36 | #define Draw XftDraw * 37 | #define Colour XftColor 38 | #define Colourmap Colormap 39 | #define Rectangle XRectangle 40 | 41 | #if defined(__linux) 42 | #include 43 | #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 44 | #include 45 | #elif defined(__FreeBSD__) || defined(__DragonFly__) 46 | #include 47 | #endif 48 | 49 | #ifndef NO_PROC_POLL 50 | /* for basename */ 51 | #include 52 | /* for getproc */ 53 | #if defined(__linux__) 54 | #include 55 | #include 56 | #endif 57 | #endif 58 | 59 | 60 | /* XEMBED messages */ 61 | #define XEMBED_FOCUS_IN 4 62 | #define XEMBED_FOCUS_OUT 5 63 | 64 | /* Arbitrary sizes */ 65 | #define UTF_SIZ 4 66 | #define ESC_BUF_SIZ (128*UTF_SIZ) 67 | #define ESC_ARG_SIZ 16 68 | #define STR_BUF_SIZ ESC_BUF_SIZ 69 | #define STR_ARG_SIZ ESC_ARG_SIZ 70 | #define DRAW_BUF_SIZ 20*1024 71 | #define XK_ANY_MOD UINT_MAX 72 | #define XK_NO_MOD 0 73 | #define XK_SWITCH_MOD (1<<13) 74 | 75 | #define REDRAW_TIMEOUT (80*1000) /* 80 ms */ 76 | 77 | /* macros */ 78 | #define SERRNO strerror(errno) 79 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 80 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 81 | #define LEN(a) (sizeof(a) / sizeof(a[0])) 82 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 83 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 84 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 85 | #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) 86 | #define IS_SET(t, flag) (((t)->mode & (flag)) != 0) 87 | #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_usec-t2.tv_usec)/1000) 88 | 89 | #define VT102ID "\033[?6c" 90 | 91 | #define OPTIMIZE_RENDER 92 | 93 | enum glyph_attribute { 94 | ATTR_NULL = 0, 95 | ATTR_REVERSE = 1, 96 | ATTR_UNDERLINE = 2, 97 | ATTR_BOLD = 4, 98 | ATTR_GFX = 8, 99 | ATTR_ITALIC = 16, 100 | ATTR_BLINK = 32, 101 | ATTR_WRAP = 64, 102 | }; 103 | 104 | enum cursor_movement { 105 | CURSOR_SAVE, 106 | CURSOR_LOAD 107 | }; 108 | 109 | enum cursor_state { 110 | CURSOR_DEFAULT = 0, 111 | CURSOR_WRAPNEXT = 1, 112 | CURSOR_ORIGIN = 2 113 | }; 114 | 115 | enum term_mode { 116 | MODE_WRAP = 1, 117 | MODE_INSERT = 2, 118 | MODE_APPKEYPAD = 4, 119 | MODE_ALTSCREEN = 8, 120 | MODE_CRLF = 16, 121 | MODE_MOUSEBTN = 32, 122 | MODE_MOUSEMOTION = 64, 123 | MODE_MOUSE = 32|64, 124 | MODE_REVERSE = 128, 125 | MODE_KBDLOCK = 256, 126 | MODE_HIDE = 512, 127 | MODE_ECHO = 1024, 128 | MODE_APPCURSOR = 2048, 129 | MODE_MOUSESGR = 4096, 130 | MODE_8BIT = 8192, 131 | MODE_BLINK = 16384, 132 | MODE_FBLINK = 32768, 133 | }; 134 | 135 | enum escape_state { 136 | ESC_START = 1, 137 | ESC_CSI = 2, 138 | ESC_STR = 4, /* DSC, OSC, PM, APC */ 139 | ESC_ALTCHARSET = 8, 140 | ESC_STR_END = 16, /* a final string was encountered */ 141 | ESC_TEST = 32, /* Enter in test mode */ 142 | }; 143 | 144 | enum window_state { 145 | WIN_VISIBLE = 1, 146 | WIN_REDRAW = 2, 147 | WIN_FOCUSED = 4 148 | }; 149 | 150 | enum selection_type { 151 | SEL_REGULAR = 1, 152 | SEL_RECTANGULAR = 2 153 | }; 154 | 155 | enum selection_snap { 156 | SNAP_WORD = 1, 157 | SNAP_LINE = 2 158 | }; 159 | 160 | /* bit macro */ 161 | #undef B0 162 | enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 }; 163 | 164 | typedef unsigned char uchar; 165 | typedef unsigned int uint; 166 | typedef unsigned long ulong; 167 | typedef unsigned short ushort; 168 | 169 | typedef struct { 170 | char c[UTF_SIZ]; /* character code */ 171 | uchar mode; /* attribute flags */ 172 | ushort fg; /* foreground */ 173 | ushort bg; /* background */ 174 | } Glyph; 175 | 176 | typedef Glyph *Line; 177 | 178 | typedef struct { 179 | Glyph attr; /* current char attributes */ 180 | int x; 181 | int y; 182 | char state; 183 | } TCursor; 184 | 185 | /* CSI Escape sequence structs */ 186 | /* ESC '[' [[ [] [;]] ] */ 187 | typedef struct { 188 | char buf[ESC_BUF_SIZ]; /* raw string */ 189 | int len; /* raw string length */ 190 | char priv; 191 | int arg[ESC_ARG_SIZ]; 192 | int narg; /* nb of args */ 193 | char mode; 194 | char prefix; 195 | } CSIEscape; 196 | 197 | /* STR Escape sequence structs */ 198 | /* ESC type [[ [] [;]] ] ESC '\' */ 199 | typedef struct { 200 | char type; /* ESC type ... */ 201 | char buf[STR_BUF_SIZ]; /* raw string */ 202 | int len; /* raw string length */ 203 | char *args[STR_ARG_SIZ]; 204 | int narg; /* nb of args */ 205 | } STREscape; 206 | 207 | /* Internal representation of the screen */ 208 | typedef struct _Term { 209 | int row; /* nb row */ 210 | int col; /* nb col */ 211 | Line *line; /* screen */ 212 | Line *alt; /* alternate screen */ 213 | bool *dirty; /* dirtyness of lines */ 214 | TCursor c; /* cursor */ 215 | int top; /* top scroll limit */ 216 | int bot; /* bottom scroll limit */ 217 | int mode; /* terminal mode flags */ 218 | int esc; /* escape state flags */ 219 | bool numlock; /* lock numbers in keyboard */ 220 | bool *tabs; 221 | struct _Term *next; 222 | int cmdfd; 223 | pid_t pid; 224 | int ybase; 225 | Line *sb; 226 | int sb_total; 227 | int sb_pos; 228 | Line *last_line; 229 | bool has_activity; 230 | char *title; 231 | #ifdef OPTIMIZE_RENDER 232 | ssize_t last_ret; 233 | bool swapped_lines; 234 | #endif 235 | } Term; 236 | 237 | /* Purely graphic info */ 238 | typedef struct { 239 | Display *dpy; 240 | Colourmap cmap; 241 | Window win; 242 | Drawable buf; 243 | Atom xembed, wmdeletewin; 244 | XIM xim; 245 | XIC xic; 246 | Draw draw; 247 | Visual *vis; 248 | int scr; 249 | bool isfixed; /* is fixed geometry? */ 250 | int fx, fy, fw, fh; /* fixed geometry */ 251 | int tw, th; /* tty width and height */ 252 | int w, h; /* window width and height */ 253 | int ch; /* char height */ 254 | int cw; /* char width */ 255 | char state; /* focus, redraw, visible */ 256 | } XWindow; 257 | 258 | typedef struct { 259 | int b; 260 | uint mask; 261 | char s[ESC_BUF_SIZ]; 262 | } Mousekey; 263 | 264 | typedef struct { 265 | KeySym k; 266 | uint mask; 267 | char s[ESC_BUF_SIZ]; 268 | /* three valued logic variables: 0 indifferent, 1 on, -1 off */ 269 | signed char appkey; /* application keypad */ 270 | signed char appcursor; /* application cursor */ 271 | signed char crlf; /* crlf mode */ 272 | } Key; 273 | 274 | /* TODO: use better name for vars... */ 275 | typedef struct { 276 | int mode; 277 | int type; 278 | int snap; 279 | int bx, by; 280 | int ex, ey; 281 | struct { 282 | int x, y; 283 | } b, e; 284 | char *clip; 285 | Atom xtarget; 286 | bool alt; 287 | struct timeval tclick1; 288 | struct timeval tclick2; 289 | } Selection; 290 | 291 | typedef union { 292 | int i; 293 | unsigned int ui; 294 | float f; 295 | const void *v; 296 | } Arg; 297 | 298 | typedef struct { 299 | unsigned int mod; 300 | KeySym keysym; 301 | void (*func)(const Arg *); 302 | const Arg arg; 303 | } Shortcut; 304 | 305 | /* function definitions used in config.h */ 306 | static void clippaste(const Arg *); 307 | static void numlock(const Arg *); 308 | static void selpaste(const Arg *); 309 | static void xzoom(const Arg *); 310 | 311 | /* Config.h for applying patches and the configuration. */ 312 | #include "config.h" 313 | 314 | /* Font structure */ 315 | typedef struct { 316 | int height; 317 | int width; 318 | int ascent; 319 | int descent; 320 | short lbearing; 321 | short rbearing; 322 | XftFont *match; 323 | FcFontSet *set; 324 | FcPattern *pattern; 325 | } Font; 326 | 327 | /* Drawing Context */ 328 | typedef struct { 329 | Colour col[LEN(colorname) < 256 ? 256 : LEN(colorname)]; 330 | Font font, bfont, ifont, ibfont; 331 | GC gc; 332 | } DC; 333 | 334 | static void die(const char *, ...); 335 | static void draw(void); 336 | static void redraw(int); 337 | static void drawregion(int, int, int, int); 338 | static void execsh(void); 339 | #ifdef NO_TABS 340 | static void sigchld(int); 341 | #endif 342 | static void run(void); 343 | #ifndef NO_PROC_POLL 344 | static char *getproc(int, char *); 345 | #endif 346 | 347 | static void csidump(void); 348 | static void csihandle(Term *); 349 | static void csiparse(void); 350 | static void csireset(void); 351 | static void strdump(void); 352 | static void strhandle(Term *); 353 | static void strparse(void); 354 | static void strreset(void); 355 | 356 | static int tattrset(Term *, int); 357 | static void tclearregion(Term *, int, int, int, int); 358 | static void tcursor(Term *, int); 359 | static void tdeletechar(Term *, int); 360 | static void tdeleteline(Term *, int); 361 | static void tinsertblank(Term *, int); 362 | static void tinsertblankline(Term *, int); 363 | static void tmoveto(Term *, int, int); 364 | static void tmoveato(Term *, int x, int y); 365 | static void tnew(Term *, int, int); 366 | static void tnewline(Term *, int); 367 | static void tputtab(Term *, bool); 368 | static void tputc(Term *, char *, int); 369 | static void treset(Term *); 370 | static int tresize(Term *, int, int); 371 | static void tscrollup(Term *, int, int); 372 | static void tscrolldown(Term *, int, int); 373 | static void tsetattr(Term *, int*, int); 374 | static void tsetchar(Term *, char *, Glyph *, int, int); 375 | static void tsetscroll(Term *, int, int); 376 | static void tswapscreen(Term *); 377 | static void tsetdirt(Term *, int, int); 378 | static void tsetdirtattr(Term *, int); 379 | static void tsetmode(Term *, bool, bool, int *, int); 380 | static void tfulldirt(Term *); 381 | static void techo(Term *, char *, int); 382 | static void tscrollback(Term *term, int n); 383 | 384 | static inline bool match(uint, uint); 385 | static void ttynew(Term *); 386 | static int ttyread(Term *); 387 | static void ttyresize(Term *); 388 | static void ttywrite(Term *, const char *, size_t); 389 | 390 | static void term_add(void); 391 | static void term_remove(Term *); 392 | static void term_focus(Term *); 393 | static void term_focus_prev(Term *); 394 | static void term_focus_next(Term *); 395 | static void term_focus_idx(int); 396 | 397 | static void xdraws(char *, Glyph, int, int, int, int); 398 | static void xhints(void); 399 | static void xclear(int, int, int, int); 400 | static void xdrawcursor(void); 401 | static void xinit(void); 402 | static void xloadcols(void); 403 | static int xsetcolorname(int, const char *); 404 | static int xloadfont(Font *, FcPattern *); 405 | static void xloadfonts(char *, int); 406 | static int xloadfontset(Font *); 407 | static void xsettitle(char *); 408 | static void xresettitle(void); 409 | static void xseturgency(int); 410 | static void xsetsel(char*); 411 | static void xtermclear(int, int, int, int); 412 | static void xunloadfont(Font *f); 413 | static void xunloadfonts(void); 414 | static void xresize(int, int); 415 | static void xdrawbar(void); 416 | static void xmove(int, int, int, int, int, int); 417 | 418 | #ifdef USE_BLANK_CURSOR 419 | static void xcursorblank(void); 420 | static void xcursorunblank(void); 421 | #endif 422 | 423 | static void expose(XEvent *); 424 | static void visibility(XEvent *); 425 | static void unmap(XEvent *); 426 | static char *kmap(KeySym, uint); 427 | static void kpress(XEvent *); 428 | static void cmessage(XEvent *); 429 | static void cresize(int, int); 430 | static void resize(XEvent *); 431 | static void focus(XEvent *); 432 | static void brelease(XEvent *); 433 | static void bpress(XEvent *); 434 | static void bmotion(XEvent *); 435 | static void selnotify(XEvent *); 436 | static void selclear(XEvent *); 437 | static void selrequest(XEvent *); 438 | 439 | static void selinit(void); 440 | static inline bool selected(int, int); 441 | static void selcopy(void); 442 | static void selscroll(Term *, int, int); 443 | static void selsnap(int, int *, int *, int); 444 | 445 | static Glyph *scrollback_get(Term *, int); 446 | static void scrollback_add(Term *, int); 447 | 448 | static int utf8decode(char *, long *); 449 | static int utf8encode(long *, char *); 450 | static int utf8size(char *); 451 | static int isfullutf8(char *, int); 452 | 453 | static ssize_t xwrite(int, char *, size_t); 454 | static void *xmalloc(size_t); 455 | static void *xrealloc(void *, size_t); 456 | static void *xcalloc(size_t, size_t); 457 | 458 | static int set_message(char *fmt, ...); 459 | 460 | static void (*handler[LASTEvent])(XEvent *) = { 461 | [KeyPress] = kpress, 462 | [ClientMessage] = cmessage, 463 | [ConfigureNotify] = resize, 464 | [VisibilityNotify] = visibility, 465 | [UnmapNotify] = unmap, 466 | [Expose] = expose, 467 | [FocusIn] = focus, 468 | [FocusOut] = focus, 469 | [MotionNotify] = bmotion, 470 | [ButtonPress] = bpress, 471 | [ButtonRelease] = brelease, 472 | [SelectionClear] = selclear, 473 | [SelectionNotify] = selnotify, 474 | [SelectionRequest] = selrequest, 475 | }; 476 | 477 | /* Globals */ 478 | static DC dc; 479 | static XWindow xw; 480 | static Term *terms; 481 | static Term *focused_term; 482 | enum tstate_t { 483 | S_NORMAL, 484 | S_PREFIX, 485 | S_RENAME, 486 | S_SELECT, 487 | S_VISUAL, 488 | S_SEARCH 489 | }; 490 | static enum tstate_t tstate = S_NORMAL; 491 | static struct { int x; int y; bool hidden; int ybase; } normal_cursor; 492 | static char *status_msg = NULL; 493 | static struct timeval status_time; 494 | #ifndef NO_PROC_POLL 495 | static struct timeval last_getproc; 496 | #endif 497 | static struct { int flag; char text[60]; int pos; } entry; 498 | static int clicked_bar = -1; 499 | static CSIEscape csiescseq; 500 | static STREscape strescseq; 501 | static pid_t pid; 502 | static Selection sel; 503 | static int iofd = -1; 504 | static char **opt_cmd = NULL; 505 | static char *opt_io = NULL; 506 | static char *opt_title = NULL; 507 | static char *opt_name = NULL; 508 | static char *opt_embed = NULL; 509 | static char *opt_class = NULL; 510 | static char *opt_font = NULL; 511 | 512 | static char *usedfont = NULL; 513 | static int usedfontsize = 0; 514 | 515 | #ifdef USE_BLANK_CURSOR 516 | static Cursor cursor; 517 | static Cursor blank_cursor; 518 | static bool hidden_cursor = false; 519 | #endif 520 | 521 | /* Font Ring Cache */ 522 | enum { 523 | FRC_NORMAL, 524 | FRC_ITALIC, 525 | FRC_BOLD, 526 | FRC_ITALICBOLD 527 | }; 528 | 529 | typedef struct { 530 | XftFont *font; 531 | long c; 532 | int flags; 533 | } Fontcache; 534 | 535 | /* 536 | * Fontcache is a ring buffer, with frccur as current position and frclen as 537 | * the current length of used elements. 538 | */ 539 | 540 | static Fontcache frc[1024]; 541 | static int frccur = -1, frclen = 0; 542 | 543 | ssize_t 544 | xwrite(int fd, char *s, size_t len) { 545 | size_t aux = len; 546 | 547 | while(len > 0) { 548 | ssize_t r = write(fd, s, len); 549 | if(r < 0) 550 | return r; 551 | len -= r; 552 | s += r; 553 | } 554 | return aux; 555 | } 556 | 557 | void * 558 | xmalloc(size_t len) { 559 | void *p = malloc(len); 560 | 561 | if(!p) 562 | die("Out of memory\n"); 563 | 564 | return p; 565 | } 566 | 567 | void * 568 | xrealloc(void *p, size_t len) { 569 | if((p = realloc(p, len)) == NULL) 570 | die("Out of memory\n"); 571 | 572 | return p; 573 | } 574 | 575 | void * 576 | xcalloc(size_t nmemb, size_t size) { 577 | void *p = calloc(nmemb, size); 578 | 579 | if(!p) 580 | die("Out of memory\n"); 581 | 582 | return p; 583 | } 584 | 585 | int 586 | utf8decode(char *s, long *u) { 587 | uchar c; 588 | int i, n, rtn; 589 | 590 | rtn = 1; 591 | c = *s; 592 | if(~c & B7) { /* 0xxxxxxx */ 593 | *u = c; 594 | return rtn; 595 | } else if((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */ 596 | *u = c&(B4|B3|B2|B1|B0); 597 | n = 1; 598 | } else if((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */ 599 | *u = c&(B3|B2|B1|B0); 600 | n = 2; 601 | } else if((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */ 602 | *u = c & (B2|B1|B0); 603 | n = 3; 604 | } else { 605 | goto invalid; 606 | } 607 | 608 | for(i = n, ++s; i > 0; --i, ++rtn, ++s) { 609 | c = *s; 610 | if((c & (B7|B6)) != B7) /* 10xxxxxx */ 611 | goto invalid; 612 | *u <<= 6; 613 | *u |= c & (B5|B4|B3|B2|B1|B0); 614 | } 615 | 616 | if((n == 1 && *u < 0x80) || 617 | (n == 2 && *u < 0x800) || 618 | (n == 3 && *u < 0x10000) || 619 | (*u >= 0xD800 && *u <= 0xDFFF)) { 620 | goto invalid; 621 | } 622 | 623 | return rtn; 624 | invalid: 625 | *u = 0xFFFD; 626 | 627 | return rtn; 628 | } 629 | 630 | int 631 | utf8encode(long *u, char *s) { 632 | uchar *sp; 633 | ulong uc; 634 | int i, n; 635 | 636 | sp = (uchar *)s; 637 | uc = *u; 638 | if(uc < 0x80) { 639 | *sp = uc; /* 0xxxxxxx */ 640 | return 1; 641 | } else if(*u < 0x800) { 642 | *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */ 643 | n = 1; 644 | } else if(uc < 0x10000) { 645 | *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */ 646 | n = 2; 647 | } else if(uc <= 0x10FFFF) { 648 | *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */ 649 | n = 3; 650 | } else { 651 | goto invalid; 652 | } 653 | 654 | for(i=n,++sp; i>0; --i,++sp) 655 | *sp = ((uc >> 6*(i-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */ 656 | 657 | return n+1; 658 | invalid: 659 | /* U+FFFD */ 660 | *s++ = '\xEF'; 661 | *s++ = '\xBF'; 662 | *s = '\xBD'; 663 | 664 | return 3; 665 | } 666 | 667 | /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode 668 | UTF-8 otherwise return 0 */ 669 | int 670 | isfullutf8(char *s, int b) { 671 | uchar *c1, *c2, *c3; 672 | 673 | c1 = (uchar *)s; 674 | c2 = (uchar *)++s; 675 | c3 = (uchar *)++s; 676 | if(b < 1) { 677 | return 0; 678 | } else if((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) { 679 | return 0; 680 | } else if((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && 681 | ((b == 1) || 682 | ((b == 2) && (*c2&(B7|B6)) == B7))) { 683 | return 0; 684 | } else if((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) && 685 | ((b == 1) || 686 | ((b == 2) && (*c2&(B7|B6)) == B7) || 687 | ((b == 3) && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) { 688 | return 0; 689 | } else { 690 | return 1; 691 | } 692 | } 693 | 694 | int 695 | utf8size(char *s) { 696 | uchar c = *s; 697 | 698 | if(~c&B7) { 699 | return 1; 700 | } else if((c&(B7|B6|B5)) == (B7|B6)) { 701 | return 2; 702 | } else if((c&(B7|B6|B5|B4)) == (B7|B6|B5)) { 703 | return 3; 704 | } else { 705 | return 4; 706 | } 707 | } 708 | 709 | void 710 | selinit(void) { 711 | memset(&sel.tclick1, 0, sizeof(sel.tclick1)); 712 | memset(&sel.tclick2, 0, sizeof(sel.tclick2)); 713 | sel.mode = 0; 714 | sel.bx = -1; 715 | sel.clip = NULL; 716 | sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 717 | if(sel.xtarget == None) 718 | sel.xtarget = XA_STRING; 719 | } 720 | 721 | static int 722 | x2col(Term *term, int x) { 723 | x -= borderpx; 724 | x /= xw.cw; 725 | 726 | return LIMIT(x, 0, term->col-1); 727 | } 728 | 729 | static int 730 | y2row(Term *term, int y) { 731 | y -= borderpx; 732 | y /= xw.ch; 733 | 734 | return LIMIT(y, 0, term->row-1); 735 | } 736 | 737 | static inline bool 738 | selected(int x, int y) { 739 | int bx, ex; 740 | 741 | if(sel.ey == y && sel.by == y) { 742 | bx = MIN(sel.bx, sel.ex); 743 | ex = MAX(sel.bx, sel.ex); 744 | 745 | return BETWEEN(x, bx, ex); 746 | } 747 | 748 | if(sel.type == SEL_RECTANGULAR) { 749 | return ((sel.b.y <= y && y <= sel.e.y) 750 | && (sel.b.x <= x && x <= sel.e.x)); 751 | } 752 | return ((sel.b.y < y && y < sel.e.y) 753 | || (y == sel.e.y && x <= sel.e.x)) 754 | || (y == sel.b.y && x >= sel.b.x 755 | && (x <= sel.e.x || sel.b.y != sel.e.y)); 756 | } 757 | 758 | void 759 | selsnap(int mode, int *x, int *y, int direction) { 760 | int i; 761 | 762 | switch(mode) { 763 | case SNAP_WORD: 764 | /* 765 | * Snap around if the word wraps around at the end or 766 | * beginning of a line. 767 | */ 768 | for(;;) { 769 | if(direction < 0 && *x <= 0) { 770 | if(*y > 0 && focused_term->line[*y - 1][focused_term->col-1].mode 771 | & ATTR_WRAP) { 772 | *y -= 1; 773 | *x = focused_term->col-1; 774 | } else { 775 | break; 776 | } 777 | } 778 | if(direction > 0 && *x >= focused_term->col-1) { 779 | if(*y < focused_term->row-1 && focused_term->line[*y][*x].mode 780 | & ATTR_WRAP) { 781 | *y += 1; 782 | *x = 0; 783 | } else { 784 | break; 785 | } 786 | } 787 | 788 | if(strchr(worddelimiters, 789 | focused_term->line[*y][*x + direction].c[0])) { 790 | break; 791 | } 792 | 793 | *x += direction; 794 | } 795 | break; 796 | case SNAP_LINE: 797 | /* 798 | * Snap around if the the previous line or the current one 799 | * has set ATTR_WRAP at its end. Then the whole next or 800 | * previous line will be selected. 801 | */ 802 | *x = (direction < 0) ? 0 : focused_term->col - 1; 803 | if(direction < 0 && *y > 0) { 804 | for(; *y > 0; *y += direction) { 805 | if(!(focused_term->line[*y-1][focused_term->col-1].mode 806 | & ATTR_WRAP)) { 807 | break; 808 | } 809 | } 810 | } else if(direction > 0 && *y < focused_term->row-1) { 811 | for(; *y < focused_term->row; *y += direction) { 812 | if(!(focused_term->line[*y][focused_term->col-1].mode 813 | & ATTR_WRAP)) { 814 | break; 815 | } 816 | } 817 | } 818 | break; 819 | default: 820 | /* 821 | * Select the whole line when the end of line is reached. 822 | */ 823 | if(direction > 0) { 824 | i = focused_term->col; 825 | while(--i > 0 && focused_term->line[*y][i].c[0] == ' ') 826 | /* nothing */; 827 | if(i > 0 && i < *x) 828 | *x = focused_term->col - 1; 829 | } 830 | break; 831 | } 832 | } 833 | 834 | void 835 | getbuttoninfo(XEvent *e) { 836 | int type; 837 | uint state = e->xbutton.state &~Button1Mask; 838 | 839 | sel.alt = IS_SET(focused_term, MODE_ALTSCREEN); 840 | 841 | sel.ex = x2col(focused_term, e->xbutton.x); 842 | sel.ey = y2row(focused_term, e->xbutton.y); 843 | 844 | if (sel.by < sel.ey 845 | || (sel.by == sel.ey && sel.bx < sel.ex)) { 846 | selsnap(sel.snap, &sel.bx, &sel.by, -1); 847 | selsnap(sel.snap, &sel.ex, &sel.ey, +1); 848 | } else { 849 | selsnap(sel.snap, &sel.ex, &sel.ey, -1); 850 | selsnap(sel.snap, &sel.bx, &sel.by, +1); 851 | } 852 | 853 | sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex; 854 | sel.b.y = MIN(sel.by, sel.ey); 855 | sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx; 856 | sel.e.y = MAX(sel.by, sel.ey); 857 | 858 | sel.type = SEL_REGULAR; 859 | for(type = 1; type < LEN(selmasks); ++type) { 860 | if(match(selmasks[type], state)) { 861 | sel.type = type; 862 | break; 863 | } 864 | } 865 | } 866 | 867 | void 868 | mousereport(XEvent *e) { 869 | int x = x2col(focused_term, e->xbutton.x), y = y2row(focused_term, e->xbutton.y), 870 | button = e->xbutton.button, state = e->xbutton.state, 871 | len; 872 | char buf[40]; 873 | static int ob, ox, oy; 874 | 875 | /* from urxvt */ 876 | if(e->xbutton.type == MotionNotify) { 877 | if(!IS_SET(focused_term, MODE_MOUSEMOTION) || (x == ox && y == oy)) 878 | return; 879 | button = ob + 32; 880 | ox = x, oy = y; 881 | } else if(!IS_SET(focused_term, MODE_MOUSESGR) 882 | && (e->xbutton.type == ButtonRelease 883 | || button == AnyButton)) { 884 | button = 3; 885 | } else { 886 | button -= Button1; 887 | if(button >= 3) 888 | button += 64 - 3; 889 | if(e->xbutton.type == ButtonPress) { 890 | ob = button; 891 | ox = x, oy = y; 892 | } 893 | } 894 | 895 | button += (state & ShiftMask ? 4 : 0) 896 | + (state & Mod4Mask ? 8 : 0) 897 | + (state & ControlMask ? 16 : 0); 898 | 899 | len = 0; 900 | if(IS_SET(focused_term, MODE_MOUSESGR)) { 901 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 902 | button, x+1, y+1, 903 | e->xbutton.type == ButtonRelease ? 'm' : 'M'); 904 | } else if(x < 223 && y < 223) { 905 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 906 | 32+button, 32+x+1, 32+y+1); 907 | } else { 908 | return; 909 | } 910 | 911 | ttywrite(focused_term, buf, len); 912 | } 913 | 914 | void 915 | bpress(XEvent *e) { 916 | struct timeval now; 917 | Mousekey *mk; 918 | 919 | if(tstate == S_NORMAL && IS_SET(focused_term, MODE_MOUSE)) { 920 | mousereport(e); 921 | return; 922 | } 923 | 924 | for(mk = mshortcuts; mk < mshortcuts + LEN(mshortcuts); mk++) { 925 | if(e->xbutton.button == mk->b 926 | && match(mk->mask, e->xbutton.state)) { 927 | ttywrite(focused_term, mk->s, strlen(mk->s)); 928 | if(IS_SET(focused_term, MODE_ECHO)) 929 | techo(focused_term, mk->s, strlen(mk->s)); 930 | return; 931 | } 932 | } 933 | 934 | if(e->xbutton.button == Button1) { 935 | if ((e->xbutton.y - borderpx) / xw.ch >= focused_term->row) { 936 | clicked_bar = x2col(focused_term, e->xbutton.x); 937 | xdrawbar(); 938 | return; 939 | } 940 | 941 | gettimeofday(&now, NULL); 942 | 943 | /* Clear previous selection, logically and visually. */ 944 | if(sel.bx != -1) { 945 | sel.bx = -1; 946 | tsetdirt(focused_term, sel.b.y, sel.e.y); 947 | draw(); 948 | } 949 | sel.mode = 1; 950 | sel.type = SEL_REGULAR; 951 | sel.ex = sel.bx = x2col(focused_term, e->xbutton.x); 952 | sel.ey = sel.by = y2row(focused_term, e->xbutton.y); 953 | 954 | /* 955 | * If the user clicks below predefined timeouts specific 956 | * snapping behaviour is exposed. 957 | */ 958 | if(TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { 959 | sel.snap = SNAP_LINE; 960 | } else if(TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { 961 | sel.snap = SNAP_WORD; 962 | } else { 963 | sel.snap = 0; 964 | } 965 | selsnap(sel.snap, &sel.bx, &sel.by, -1); 966 | selsnap(sel.snap, &sel.ex, &sel.ey, +1); 967 | sel.b.x = sel.bx; 968 | sel.b.y = sel.by; 969 | sel.e.x = sel.ex; 970 | sel.e.y = sel.ey; 971 | 972 | /* 973 | * Draw selection, unless it's regular and we don't want to 974 | * make clicks visible 975 | */ 976 | if(sel.snap != 0) { 977 | sel.mode++; 978 | tsetdirt(focused_term, sel.b.y, sel.e.y); 979 | draw(); 980 | } 981 | sel.tclick2 = sel.tclick1; 982 | sel.tclick1 = now; 983 | } else if (e->xbutton.button == Button4) { 984 | tscrollback(focused_term, -(focused_term->row / 2)); 985 | } else if (e->xbutton.button == Button5) { 986 | tscrollback(focused_term, focused_term->row / 2); 987 | } 988 | } 989 | 990 | void 991 | selcopy(void) { 992 | char *str, *ptr; 993 | int x, y, bufsize, size, i, ex; 994 | Glyph *gp, *last; 995 | 996 | // TODO: Handle selection grabbing across scrollback. 997 | 998 | if(sel.bx == -1) { 999 | str = NULL; 1000 | } else { 1001 | bufsize = (focused_term->col+1) * (sel.e.y-sel.b.y+1) * UTF_SIZ; 1002 | ptr = str = xmalloc(bufsize); 1003 | 1004 | /* append every set & selected glyph to the selection */ 1005 | for(y = sel.b.y; y < sel.e.y + 1; y++) { 1006 | gp = &focused_term->line[y][0]; 1007 | last = gp + focused_term->col; 1008 | 1009 | while(--last >= gp && !(selected(last - gp, y) && \ 1010 | strcmp(last->c, " ") != 0)) 1011 | /* nothing */; 1012 | 1013 | for(x = 0; gp <= last; x++, ++gp) { 1014 | if(!selected(x, y)) 1015 | continue; 1016 | 1017 | size = utf8size(gp->c); 1018 | memcpy(ptr, gp->c, size); 1019 | ptr += size; 1020 | } 1021 | 1022 | /* 1023 | * Copy and pasting of line endings is inconsistent 1024 | * in the inconsistent terminal and GUI world. 1025 | * The best solution seems like to produce '\n' when 1026 | * something is copied from st and convert '\n' to 1027 | * '\r', when something to be pasted is received by 1028 | * st. 1029 | * FIXME: Fix the computer world. 1030 | */ 1031 | if(y < sel.e.y && !((gp-1)->mode & ATTR_WRAP)) 1032 | *ptr++ = '\n'; 1033 | 1034 | /* 1035 | * If the last selected line expands in the selection 1036 | * after the visible text '\n' is appended. 1037 | */ 1038 | if(y == sel.e.y) { 1039 | i = focused_term->col; 1040 | while(--i > 0 && focused_term->line[y][i].c[0] == ' ') 1041 | /* nothing */; 1042 | ex = sel.e.x; 1043 | if(sel.b.y == sel.e.y && sel.e.x < sel.b.x) 1044 | ex = sel.b.x; 1045 | if(i < ex) 1046 | *ptr++ = '\n'; 1047 | } 1048 | } 1049 | *ptr = 0; 1050 | } 1051 | xsetsel(str); 1052 | } 1053 | 1054 | void 1055 | selnotify(XEvent *e) { 1056 | ulong nitems, ofs, rem; 1057 | int format; 1058 | uchar *data, *last, *repl; 1059 | Atom type; 1060 | 1061 | ofs = 0; 1062 | do { 1063 | if(XGetWindowProperty(xw.dpy, xw.win, XA_PRIMARY, ofs, BUFSIZ/4, 1064 | False, AnyPropertyType, &type, &format, 1065 | &nitems, &rem, &data)) { 1066 | fprintf(stderr, "Clipboard allocation failed\n"); 1067 | return; 1068 | } 1069 | 1070 | /* 1071 | * As seen in selcopy: 1072 | * Line endings are inconsistent in the terminal and GUI world 1073 | * copy and pasting. When receiving some selection data, 1074 | * replace all '\n' with '\r'. 1075 | * FIXME: Fix the computer world. 1076 | */ 1077 | repl = data; 1078 | last = data + nitems * format / 8; 1079 | while((repl = memchr(repl, '\n', last - repl))) { 1080 | *repl++ = '\r'; 1081 | } 1082 | 1083 | ttywrite(focused_term, (const char *)data, nitems * format / 8); 1084 | XFree(data); 1085 | /* number of 32-bit chunks returned */ 1086 | ofs += nitems * format / 32; 1087 | } while(rem > 0); 1088 | } 1089 | 1090 | void 1091 | selpaste(const Arg *dummy) { 1092 | XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, 1093 | xw.win, CurrentTime); 1094 | } 1095 | 1096 | void 1097 | clippaste(const Arg *dummy) { 1098 | Atom clipboard; 1099 | 1100 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1101 | XConvertSelection(xw.dpy, clipboard, sel.xtarget, XA_PRIMARY, 1102 | xw.win, CurrentTime); 1103 | } 1104 | 1105 | void 1106 | selclear(XEvent *e) { 1107 | if(sel.bx == -1) 1108 | return; 1109 | sel.bx = -1; 1110 | tsetdirt(focused_term, sel.b.y, sel.e.y); 1111 | } 1112 | 1113 | void 1114 | selrequest(XEvent *e) { 1115 | XSelectionRequestEvent *xsre; 1116 | XSelectionEvent xev; 1117 | Atom xa_targets, string; 1118 | 1119 | xsre = (XSelectionRequestEvent *) e; 1120 | xev.type = SelectionNotify; 1121 | xev.requestor = xsre->requestor; 1122 | xev.selection = xsre->selection; 1123 | xev.target = xsre->target; 1124 | xev.time = xsre->time; 1125 | /* reject */ 1126 | xev.property = None; 1127 | 1128 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 1129 | if(xsre->target == xa_targets) { 1130 | /* respond with the supported type */ 1131 | string = sel.xtarget; 1132 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 1133 | XA_ATOM, 32, PropModeReplace, 1134 | (uchar *) &string, 1); 1135 | xev.property = xsre->property; 1136 | } else if(xsre->target == sel.xtarget && sel.clip != NULL) { 1137 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 1138 | xsre->target, 8, PropModeReplace, 1139 | (uchar *) sel.clip, strlen(sel.clip)); 1140 | xev.property = xsre->property; 1141 | } 1142 | 1143 | /* all done, send a notification to the listener */ 1144 | if(!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev)) 1145 | fprintf(stderr, "Error sending SelectionNotify event\n"); 1146 | } 1147 | 1148 | void 1149 | xsetsel(char *str) { 1150 | /* register the selection for both the clipboard and the primary */ 1151 | Atom clipboard; 1152 | 1153 | free(sel.clip); 1154 | sel.clip = str; 1155 | 1156 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime); 1157 | 1158 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1159 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 1160 | } 1161 | 1162 | void 1163 | brelease(XEvent *e) { 1164 | if(tstate == S_NORMAL && IS_SET(focused_term, MODE_MOUSE)) { 1165 | mousereport(e); 1166 | return; 1167 | } 1168 | 1169 | if(e->xbutton.button == Button2) { 1170 | selpaste(NULL); 1171 | } else if(e->xbutton.button == Button1) { 1172 | if(sel.mode < 2) { 1173 | sel.bx = -1; 1174 | } else { 1175 | getbuttoninfo(e); 1176 | selcopy(); 1177 | } 1178 | sel.mode = 0; 1179 | focused_term->dirty[sel.ey] = 1; 1180 | } 1181 | } 1182 | 1183 | void 1184 | bmotion(XEvent *e) { 1185 | int oldey, oldex, oldsby, oldsey; 1186 | 1187 | #ifdef USE_BLANK_CURSOR 1188 | xcursorunblank(); 1189 | #endif 1190 | 1191 | if(tstate == S_NORMAL && IS_SET(focused_term, MODE_MOUSE)) { 1192 | mousereport(e); 1193 | return; 1194 | } 1195 | 1196 | if(!sel.mode) 1197 | return; 1198 | 1199 | sel.mode++; 1200 | oldey = sel.ey; 1201 | oldex = sel.ex; 1202 | oldsby = sel.b.y; 1203 | oldsey = sel.e.y; 1204 | getbuttoninfo(e); 1205 | 1206 | if(oldey != sel.ey || oldex != sel.ex) { 1207 | tsetdirt(focused_term, MIN(sel.b.y, oldsby), MAX(sel.e.y, oldsey)); 1208 | } 1209 | } 1210 | 1211 | void 1212 | die(const char *errstr, ...) { 1213 | va_list ap; 1214 | 1215 | va_start(ap, errstr); 1216 | vfprintf(stderr, errstr, ap); 1217 | va_end(ap); 1218 | exit(EXIT_FAILURE); 1219 | } 1220 | 1221 | void 1222 | execsh(void) { 1223 | char **args; 1224 | char *envshell = getenv("SHELL"); 1225 | const struct passwd *pass = getpwuid(getuid()); 1226 | char buf[sizeof(long) * 8 + 1]; 1227 | 1228 | unsetenv("COLUMNS"); 1229 | unsetenv("LINES"); 1230 | unsetenv("TERMCAP"); 1231 | 1232 | if(pass) { 1233 | setenv("LOGNAME", pass->pw_name, 1); 1234 | setenv("USER", pass->pw_name, 1); 1235 | setenv("SHELL", pass->pw_shell, 0); 1236 | setenv("HOME", pass->pw_dir, 0); 1237 | } 1238 | 1239 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1240 | setenv("WINDOWID", buf, 1); 1241 | 1242 | signal(SIGCHLD, SIG_DFL); 1243 | signal(SIGHUP, SIG_DFL); 1244 | signal(SIGINT, SIG_DFL); 1245 | signal(SIGQUIT, SIG_DFL); 1246 | signal(SIGTERM, SIG_DFL); 1247 | signal(SIGALRM, SIG_DFL); 1248 | 1249 | DEFAULT(envshell, shell); 1250 | setenv("TERM", termname, 1); 1251 | args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL}; 1252 | execvp(args[0], args); 1253 | exit(EXIT_FAILURE); 1254 | } 1255 | 1256 | void 1257 | sigchld(int a) { 1258 | int stat = 0; 1259 | 1260 | #ifdef NO_TABS 1261 | if(waitpid(pid, &stat, 0) < 0) 1262 | die("Waiting for pid %hd failed: %s\n", pid, SERRNO); 1263 | 1264 | if(WIFEXITED(stat)) { 1265 | exit(WEXITSTATUS(stat)); 1266 | } else { 1267 | exit(EXIT_FAILURE); 1268 | } 1269 | #else 1270 | for (;;) { 1271 | pid_t pid = waitpid(-1, &stat, WNOHANG); 1272 | if (pid <= 0) break; 1273 | 1274 | if (!terms) exit(EXIT_SUCCESS); 1275 | 1276 | Term *term; 1277 | for (term = terms; term; term = term->next) { 1278 | if (term->pid == pid) break; 1279 | } 1280 | if (!term) die("bad pid from sigchld\n"); 1281 | 1282 | if(WIFEXITED(stat)) { 1283 | // term_remove(term); 1284 | } else { 1285 | ; 1286 | } 1287 | } 1288 | #endif 1289 | } 1290 | 1291 | void 1292 | ttynew(Term *term) { 1293 | int m, s; 1294 | struct winsize w = {term->row, term->col, 0, 0}; 1295 | 1296 | /* seems to work fine on linux, openbsd and freebsd */ 1297 | if(openpty(&m, &s, NULL, NULL, &w) < 0) 1298 | die("openpty failed: %s\n", SERRNO); 1299 | 1300 | switch(pid = fork()) { 1301 | case -1: 1302 | die("fork failed\n"); 1303 | break; 1304 | case 0: 1305 | setsid(); /* create a new process group */ 1306 | dup2(s, STDIN_FILENO); 1307 | dup2(s, STDOUT_FILENO); 1308 | dup2(s, STDERR_FILENO); 1309 | if(ioctl(s, TIOCSCTTY, NULL) < 0) 1310 | die("ioctl TIOCSCTTY failed: %s\n", SERRNO); 1311 | close(s); 1312 | close(m); 1313 | execsh(); 1314 | break; 1315 | default: 1316 | close(s); 1317 | term->cmdfd = m; 1318 | term->pid = pid; 1319 | #ifdef NO_TABS 1320 | signal(SIGCHLD, sigchld); 1321 | #endif 1322 | if(opt_io) { 1323 | iofd = (!strcmp(opt_io, "-")) ? 1324 | STDOUT_FILENO : 1325 | open(opt_io, O_WRONLY | O_CREAT, 0666); 1326 | if(iofd < 0) { 1327 | fprintf(stderr, "Error opening %s:%s\n", 1328 | opt_io, strerror(errno)); 1329 | } 1330 | } 1331 | } 1332 | } 1333 | 1334 | void 1335 | dump(char c) { 1336 | static int col; 1337 | 1338 | fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.'); 1339 | if(++col % 10 == 0) 1340 | fprintf(stderr, "\n"); 1341 | } 1342 | 1343 | int 1344 | ttyread(Term *term) { 1345 | static char buf[BUFSIZ]; 1346 | static int buflen = 0; 1347 | char *ptr; 1348 | char s[UTF_SIZ]; 1349 | int charsize; /* size of utf8 char in bytes */ 1350 | long utf8c; 1351 | int ret; 1352 | 1353 | /* append read bytes to unprocessed bytes */ 1354 | if((ret = read(term->cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) { 1355 | #ifdef NO_TABS 1356 | die("Couldn't read from shell: %s\n", SERRNO); 1357 | #else 1358 | term_remove(term); 1359 | #endif 1360 | return -1; 1361 | } 1362 | 1363 | /* ignore screen output while selecting */ 1364 | if (tstate != S_NORMAL) return 0; 1365 | // TODO: Potentially buffer data here: 1366 | // if (...) xrealloc(select_buf, (select_buf_size *= 2)); 1367 | 1368 | #ifdef OPTIMIZE_RENDER 1369 | term->last_ret = ret; 1370 | #endif 1371 | 1372 | /* mark activity */ 1373 | if (showactivity && focused_term != term) { 1374 | term->has_activity = true; 1375 | } 1376 | 1377 | /* scroll down if we receive bytes while examining scrollback */ 1378 | if (term->ybase < 0) { 1379 | tscrollback(term, -term->ybase); 1380 | } 1381 | 1382 | /* process every complete utf8 char */ 1383 | buflen += ret; 1384 | ptr = buf; 1385 | while(buflen >= UTF_SIZ || isfullutf8(ptr,buflen)) { 1386 | charsize = utf8decode(ptr, &utf8c); 1387 | utf8encode(&utf8c, s); 1388 | tputc(term, s, charsize); 1389 | ptr += charsize; 1390 | buflen -= charsize; 1391 | } 1392 | 1393 | /* keep any uncomplete utf8 char for the next call */ 1394 | memmove(buf, ptr, buflen); 1395 | 1396 | return 0; 1397 | } 1398 | 1399 | void 1400 | ttywrite(Term *term, const char *s, size_t n) { 1401 | if(write(term->cmdfd, s, n) == -1) 1402 | die("write error on tty: %s\n", SERRNO); 1403 | } 1404 | 1405 | void 1406 | ttyresize(Term *term) { 1407 | struct winsize w; 1408 | 1409 | w.ws_row = term->row; 1410 | w.ws_col = term->col; 1411 | w.ws_xpixel = xw.tw; 1412 | w.ws_ypixel = xw.th; 1413 | if(ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) 1414 | fprintf(stderr, "Couldn't set window size: %s\n", SERRNO); 1415 | } 1416 | 1417 | int 1418 | tattrset(Term *term, int attr) { 1419 | int i, j; 1420 | 1421 | for(i = 0; i < term->row-1; i++) { 1422 | for(j = 0; j < term->col-1; j++) { 1423 | if(term->line[i][j].mode & attr) 1424 | return 1; 1425 | } 1426 | } 1427 | 1428 | return 0; 1429 | } 1430 | 1431 | void 1432 | tsetdirt(Term *term, int top, int bot) { 1433 | int i; 1434 | 1435 | LIMIT(top, 0, term->row-1); 1436 | LIMIT(bot, 0, term->row-1); 1437 | 1438 | for(i = top; i <= bot; i++) 1439 | term->dirty[i] = 1; 1440 | } 1441 | 1442 | void 1443 | tsetdirtattr(Term *term, int attr) { 1444 | int i, j; 1445 | 1446 | for(i = 0; i < term->row-1; i++) { 1447 | for(j = 0; j < term->col-1; j++) { 1448 | if(term->line[i][j].mode & attr) { 1449 | tsetdirt(term, i, i); 1450 | break; 1451 | } 1452 | } 1453 | } 1454 | } 1455 | 1456 | void 1457 | tfulldirt(Term *term) { 1458 | tsetdirt(term, 0, term->row-1); 1459 | } 1460 | 1461 | void 1462 | tcursor(Term *term, int mode) { 1463 | static TCursor c; 1464 | 1465 | if(mode == CURSOR_SAVE) { 1466 | c = term->c; 1467 | } else if(mode == CURSOR_LOAD) { 1468 | term->c = c; 1469 | tmoveto(term, c.x, c.y); 1470 | } 1471 | } 1472 | 1473 | void 1474 | treset(Term *term) { 1475 | uint i; 1476 | 1477 | term->c = (TCursor){{ 1478 | .mode = ATTR_NULL, 1479 | .fg = defaultfg, 1480 | .bg = defaultbg 1481 | }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1482 | 1483 | memset(term->tabs, 0, term->col * sizeof(*term->tabs)); 1484 | for(i = tabspaces; i < term->col; i += tabspaces) 1485 | term->tabs[i] = 1; 1486 | term->top = 0; 1487 | term->bot = term->row - 1; 1488 | term->mode = MODE_WRAP; 1489 | 1490 | tclearregion(term, 0, 0, term->col-1, term->row-1); 1491 | tmoveto(term, 0, 0); 1492 | tcursor(term, CURSOR_SAVE); 1493 | } 1494 | 1495 | void 1496 | tnew(Term *term, int col, int row) { 1497 | memset(term, 0, sizeof(Term)); 1498 | tresize(term, col, row); 1499 | term->numlock = 1; 1500 | 1501 | treset(term); 1502 | } 1503 | 1504 | void 1505 | tswapscreen(Term *term) { 1506 | Line *tmp = term->line; 1507 | 1508 | // TODO: Handle scrollback when the alternate buffer is active. 1509 | 1510 | term->line = term->alt; 1511 | term->alt = tmp; 1512 | term->mode ^= MODE_ALTSCREEN; 1513 | tfulldirt(term); 1514 | } 1515 | 1516 | int 1517 | set_message(char *fmt, ...) { 1518 | int ret; 1519 | 1520 | if (fmt == NULL) { 1521 | if (status_msg) free(status_msg); 1522 | status_msg = NULL; 1523 | return 0; 1524 | } 1525 | 1526 | char *text = (char *)xmalloc(100 * sizeof(char)); 1527 | memset(text, 0, 100 * sizeof(char)); 1528 | 1529 | va_list list; 1530 | va_start(list, fmt); 1531 | ret = vsnprintf(text, 100 * sizeof(char), fmt, list); 1532 | va_end(list); 1533 | 1534 | if (status_msg) free(status_msg); 1535 | status_msg = text; 1536 | gettimeofday(&status_time, NULL); 1537 | 1538 | return ret; 1539 | } 1540 | 1541 | void 1542 | tscrollback(Term *term, int n) { 1543 | if (term->mode & MODE_APPKEYPAD) { 1544 | // || (term->mode & MODE_ALTSCREEN)) { 1545 | return; 1546 | } 1547 | 1548 | int base = term->ybase; 1549 | int i, y; 1550 | 1551 | // Offset ybase, limit. 1552 | term->ybase += n; 1553 | if (term->ybase > 0) { 1554 | term->ybase = 0; 1555 | } else if (term->ybase < -term->sb_total) { 1556 | term->ybase = -term->sb_total; 1557 | } 1558 | 1559 | // Do not bother handling scrollback if the ybase didn't change. 1560 | if (term->ybase == base) { 1561 | return; 1562 | } 1563 | 1564 | // If `term->line` is still at the very bottom and we're about to switch to 1565 | // scrollback, save the current lines in a buffer to remember them. 1566 | if (base == 0) { 1567 | for (i = 0; i < term->row; i++) { 1568 | memcpy(term->last_line[i], term->line[i], term->col * sizeof(Glyph)); 1569 | } 1570 | } 1571 | 1572 | // Get the "real" line based on ybase. 1573 | for (i = 0; i < term->row; i++) { 1574 | y = i + term->ybase; 1575 | if (y < 0) { 1576 | memcpy(term->line[i], scrollback_get(term, -(y + 1)), term->col * sizeof(Glyph)); 1577 | } else { 1578 | memcpy(term->line[i], term->last_line[y], term->col * sizeof(Glyph)); 1579 | } 1580 | // This is unnecessary. The next call to redraw will handle this. 1581 | term->dirty[i] = 1; 1582 | } 1583 | 1584 | // Ensure a redraw of the screen. 1585 | if (term == focused_term) redraw(0); 1586 | } 1587 | 1588 | void 1589 | tscrolldown(Term *term, int orig, int n) { 1590 | int i; 1591 | Line temp; 1592 | 1593 | LIMIT(n, 0, term->bot-orig+1); 1594 | 1595 | #ifdef OPTIMIZE_RENDER 1596 | if (term == focused_term && !term->swapped_lines) { 1597 | // NOTE: We only allow this optimization once per draw. 1598 | // NOTE: We could also measure time. 1599 | // NOTE: Could make this an int and increment and check 1600 | // (could also do this for tsetchar optimization). 1601 | term->swapped_lines = true; 1602 | 1603 | /* need to get dirty lines written to the buffer */ 1604 | //drawregion(0, 0, term->col, term->row); 1605 | drawregion(0, orig + n - 1, term->col, term->row); 1606 | 1607 | /* remove the old cursor */ 1608 | // if (orig >= term->c.y... 1609 | xdraws( 1610 | term->line[term->c.y][term->c.x].c, 1611 | term->line[term->c.y][term->c.x], 1612 | term->c.x, term->c.y, 1, 1613 | utf8size(term->line[term->c.y][term->c.x].c)); 1614 | 1615 | for(i = term->bot; i >= orig+n; i--) { 1616 | temp = term->line[i]; 1617 | term->line[i] = term->line[i-n]; 1618 | term->line[i-n] = temp; 1619 | } 1620 | 1621 | //xmove(0, orig+n, 0, orig, term->col, term->bot-orig); 1622 | xmove(0, orig+n, 0, orig, term->col, MAX(term->bot-orig-(n-1), 0)); 1623 | tclearregion(term, 0, orig, term->col-1, orig+n-1); 1624 | 1625 | selscroll(term, orig, n); 1626 | return; 1627 | } 1628 | #endif 1629 | 1630 | tclearregion(term, 0, term->bot-n+1, term->col-1, term->bot); 1631 | 1632 | for(i = term->bot; i >= orig+n; i--) { 1633 | temp = term->line[i]; 1634 | term->line[i] = term->line[i-n]; 1635 | term->line[i-n] = temp; 1636 | 1637 | term->dirty[i] = 1; 1638 | term->dirty[i-n] = 1; 1639 | } 1640 | 1641 | selscroll(term, orig, n); 1642 | } 1643 | 1644 | Glyph * 1645 | scrollback_get(Term *term, int i) { 1646 | i = term->sb_pos - 1 - i; 1647 | if (i < 0) { 1648 | i = term->sb_total + i; 1649 | if (i < term->sb_pos || term->sb_total != scrollback) { 1650 | die("bad scrollback!\n"); 1651 | } 1652 | } 1653 | return term->sb[i]; 1654 | } 1655 | 1656 | void 1657 | scrollback_add(Term *term, int i) { 1658 | if (term->sb_pos == scrollback) { 1659 | term->sb_pos = 0; 1660 | } 1661 | memcpy(term->sb[term->sb_pos], term->line[i], term->col * sizeof(Glyph)); 1662 | term->sb_pos++; 1663 | if (term->sb_total != scrollback) { 1664 | term->sb_total++; 1665 | } 1666 | } 1667 | 1668 | void 1669 | tscrollup(Term *term, int orig, int n) { 1670 | int i; 1671 | Line temp; 1672 | LIMIT(n, 0, term->bot-orig+1); 1673 | 1674 | if (orig == term->top && term->ybase == 0 && !(term->mode & MODE_APPKEYPAD)) { 1675 | // && !(term->mode & MODE_ALTSCREEN)) { 1676 | for(i = orig; i <= orig + n - 1; i++) { 1677 | scrollback_add(term, i); 1678 | } 1679 | } 1680 | 1681 | #ifdef OPTIMIZE_RENDER 1682 | if (term == focused_term && !term->swapped_lines) { 1683 | // NOTE: We only allow this optimization once per draw. 1684 | // NOTE: We could also measure time. 1685 | // NOTE: Could make this an int and increment and check 1686 | // (could also do this for tsetchar optimization). 1687 | term->swapped_lines = true; 1688 | 1689 | /* need to get dirty lines written to the buffer */ 1690 | //drawregion(0, 0, term->col, term->row); 1691 | drawregion(0, orig + n - 1, term->col, term->row); 1692 | 1693 | /* remove the old cursor */ 1694 | // if (orig >= term->c.y... 1695 | xdraws( 1696 | term->line[term->c.y][term->c.x].c, 1697 | term->line[term->c.y][term->c.x], 1698 | term->c.x, term->c.y, 1, 1699 | utf8size(term->line[term->c.y][term->c.x].c)); 1700 | 1701 | for(i = orig; i <= term->bot-n; i++) { 1702 | temp = term->line[i]; 1703 | term->line[i] = term->line[i+n]; 1704 | term->line[i+n] = temp; 1705 | } 1706 | 1707 | //xmove(0, orig, 0, orig+n, term->col, term->bot-orig); 1708 | xmove(0, orig, 0, orig+n, term->col, MAX(term->bot-orig-(n-1), 0)); 1709 | tclearregion(term, 0, term->bot-n+1, term->col-1, term->bot); 1710 | 1711 | selscroll(term, orig, -n); 1712 | 1713 | return; 1714 | } 1715 | #endif 1716 | 1717 | tclearregion(term, 0, orig, term->col-1, orig+n-1); 1718 | 1719 | for(i = orig; i <= term->bot-n; i++) { 1720 | temp = term->line[i]; 1721 | term->line[i] = term->line[i+n]; 1722 | term->line[i+n] = temp; 1723 | 1724 | term->dirty[i] = 1; 1725 | term->dirty[i+n] = 1; 1726 | } 1727 | 1728 | selscroll(term, orig, -n); 1729 | } 1730 | 1731 | void 1732 | selscroll(Term *term, int orig, int n) { 1733 | if(sel.bx == -1) 1734 | return; 1735 | 1736 | if(BETWEEN(sel.by, orig, term->bot) || BETWEEN(sel.ey, orig, term->bot)) { 1737 | if((sel.by += n) > term->bot || (sel.ey += n) < term->top) { 1738 | sel.bx = -1; 1739 | return; 1740 | } 1741 | if(sel.type == SEL_RECTANGULAR) { 1742 | if(sel.by < term->top) 1743 | sel.by = term->top; 1744 | if(sel.ey > term->bot) 1745 | sel.ey = term->bot; 1746 | } else { 1747 | if(sel.by < term->top) { 1748 | sel.by = term->top; 1749 | sel.bx = 0; 1750 | } 1751 | if(sel.ey > term->bot) { 1752 | sel.ey = term->bot; 1753 | sel.ex = term->col; 1754 | } 1755 | } 1756 | sel.b.y = sel.by, sel.b.x = sel.bx; 1757 | sel.e.y = sel.ey, sel.e.x = sel.ex; 1758 | } 1759 | } 1760 | 1761 | void 1762 | tnewline(Term *term, int first_col) { 1763 | int y = term->c.y; 1764 | 1765 | if(y == term->bot) { 1766 | tscrollup(term, term->top, 1); 1767 | } else { 1768 | y++; 1769 | } 1770 | tmoveto(term, first_col ? 0 : term->c.x, y); 1771 | } 1772 | 1773 | void 1774 | csiparse(void) { 1775 | char *p = csiescseq.buf, *np; 1776 | long int v; 1777 | 1778 | csiescseq.narg = 0; 1779 | if(*p == '?') { 1780 | csiescseq.priv = 1; 1781 | p++; 1782 | } else if(*p == '>') { 1783 | csiescseq.prefix = '>'; 1784 | p++; 1785 | } 1786 | 1787 | csiescseq.buf[csiescseq.len] = '\0'; 1788 | while(p < csiescseq.buf+csiescseq.len) { 1789 | np = NULL; 1790 | v = strtol(p, &np, 10); 1791 | if(np == p) 1792 | v = 0; 1793 | if(v == LONG_MAX || v == LONG_MIN) 1794 | v = -1; 1795 | csiescseq.arg[csiescseq.narg++] = v; 1796 | p = np; 1797 | if(*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1798 | break; 1799 | p++; 1800 | } 1801 | csiescseq.mode = *p; 1802 | } 1803 | 1804 | /* for absolute user moves, when decom is set */ 1805 | void 1806 | tmoveato(Term *term, int x, int y) { 1807 | tmoveto(term, x, y + ((term->c.state & CURSOR_ORIGIN) ? term->top: 0)); 1808 | } 1809 | 1810 | void 1811 | tmoveto(Term *term, int x, int y) { 1812 | int miny, maxy; 1813 | 1814 | if(term->c.state & CURSOR_ORIGIN) { 1815 | miny = term->top; 1816 | maxy = term->bot; 1817 | } else { 1818 | miny = 0; 1819 | maxy = term->row - 1; 1820 | } 1821 | LIMIT(x, 0, term->col-1); 1822 | LIMIT(y, miny, maxy); 1823 | term->c.state &= ~CURSOR_WRAPNEXT; 1824 | term->c.x = x; 1825 | term->c.y = y; 1826 | } 1827 | 1828 | void 1829 | tsetchar(Term *term, char *c, Glyph *attr, int x, int y) { 1830 | static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1831 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1832 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1833 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1834 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1835 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1836 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1837 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1838 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1839 | }; 1840 | 1841 | /* 1842 | * The table is proudly stolen from rxvt. 1843 | */ 1844 | if(attr->mode & ATTR_GFX) { 1845 | if(c[0] >= 0x41 && c[0] <= 0x7e 1846 | && vt100_0[c[0] - 0x41]) { 1847 | c = vt100_0[c[0] - 0x41]; 1848 | } 1849 | } 1850 | 1851 | #ifdef OPTIMIZE_RENDER 1852 | //if (term == focused_term) { 1853 | if (term == focused_term && term->last_ret > (term->row * term->col) / 2) { 1854 | term->line[y][x] = *attr; 1855 | memcpy(term->line[y][x].c, c, UTF_SIZ); 1856 | xdraws( 1857 | term->line[y][x].c, 1858 | term->line[y][x], x, y, 1, 1859 | utf8size(term->line[y][x].c)); 1860 | return; 1861 | } 1862 | #endif 1863 | 1864 | term->dirty[y] = 1; 1865 | term->line[y][x] = *attr; 1866 | memcpy(term->line[y][x].c, c, UTF_SIZ); 1867 | } 1868 | 1869 | void 1870 | tclearregion(Term *term, int x1, int y1, int x2, int y2) { 1871 | int x, y, temp; 1872 | 1873 | if(x1 > x2) 1874 | temp = x1, x1 = x2, x2 = temp; 1875 | if(y1 > y2) 1876 | temp = y1, y1 = y2, y2 = temp; 1877 | 1878 | LIMIT(x1, 0, term->col-1); 1879 | LIMIT(x2, 0, term->col-1); 1880 | LIMIT(y1, 0, term->row-1); 1881 | LIMIT(y2, 0, term->row-1); 1882 | 1883 | #ifdef OPTIMIZE_RENDER_ 1884 | if (y2 - y1 <= 5 && term == focused_term) { 1885 | for(y = y1; y <= y2; y++) { 1886 | for(x = x1; x <= x2; x++) { 1887 | if(selected(x, y)) 1888 | selclear(NULL); 1889 | term->line[y][x] = term->c.attr; 1890 | memcpy(term->line[y][x].c, " ", 2); 1891 | if (xw.draw) xdraws(term->line[y][x].c, term->line[y][x], x, y, 1, 1); 1892 | } 1893 | } 1894 | return; 1895 | } 1896 | #endif 1897 | 1898 | for(y = y1; y <= y2; y++) { 1899 | term->dirty[y] = 1; 1900 | for(x = x1; x <= x2; x++) { 1901 | if(selected(x, y)) 1902 | selclear(NULL); 1903 | term->line[y][x] = term->c.attr; 1904 | memcpy(term->line[y][x].c, " ", 2); 1905 | } 1906 | } 1907 | } 1908 | 1909 | void 1910 | tdeletechar(Term *term, int n) { 1911 | int src = term->c.x + n; 1912 | int dst = term->c.x; 1913 | int size = term->col - src; 1914 | 1915 | #ifndef OPTIMIZE_RENDER_ 1916 | // NOTE: Probably useless anyway since tclearregion sets dirty flag anyway. 1917 | term->dirty[term->c.y] = 1; 1918 | #endif 1919 | 1920 | if(src >= term->col) { 1921 | tclearregion(term, term->c.x, term->c.y, term->col-1, term->c.y); 1922 | return; 1923 | } 1924 | 1925 | memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], 1926 | size * sizeof(Glyph)); 1927 | tclearregion(term, term->col-n, term->c.y, term->col-1, term->c.y); 1928 | } 1929 | 1930 | void 1931 | tinsertblank(Term *term, int n) { 1932 | int src = term->c.x; 1933 | int dst = src + n; 1934 | int size = term->col - dst; 1935 | 1936 | #ifndef OPTIMIZE_RENDER_ 1937 | // NOTE: Probably useless anyway since tclearregion sets dirty flag anyway. 1938 | term->dirty[term->c.y] = 1; 1939 | #endif 1940 | 1941 | if(dst >= term->col) { 1942 | tclearregion(term, term->c.x, term->c.y, term->col-1, term->c.y); 1943 | return; 1944 | } 1945 | 1946 | memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], 1947 | size * sizeof(Glyph)); 1948 | tclearregion(term, src, term->c.y, dst - 1, term->c.y); 1949 | } 1950 | 1951 | void 1952 | tinsertblankline(Term *term, int n) { 1953 | if(term->c.y < term->top || term->c.y > term->bot) 1954 | return; 1955 | 1956 | tscrolldown(term, term->c.y, n); 1957 | } 1958 | 1959 | void 1960 | tdeleteline(Term *term, int n) { 1961 | if(term->c.y < term->top || term->c.y > term->bot) 1962 | return; 1963 | 1964 | tscrollup(term, term->c.y, n); 1965 | } 1966 | 1967 | void 1968 | tsetattr(Term *term, int *attr, int l) { 1969 | int i; 1970 | 1971 | for(i = 0; i < l; i++) { 1972 | switch(attr[i]) { 1973 | case 0: 1974 | term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE \ 1975 | | ATTR_BOLD | ATTR_ITALIC \ 1976 | | ATTR_BLINK); 1977 | term->c.attr.fg = defaultfg; 1978 | term->c.attr.bg = defaultbg; 1979 | break; 1980 | case 1: 1981 | term->c.attr.mode |= ATTR_BOLD; 1982 | break; 1983 | case 3: 1984 | term->c.attr.mode |= ATTR_ITALIC; 1985 | break; 1986 | case 4: 1987 | term->c.attr.mode |= ATTR_UNDERLINE; 1988 | break; 1989 | case 5: /* slow blink */ 1990 | case 6: /* rapid blink */ 1991 | term->c.attr.mode |= ATTR_BLINK; 1992 | break; 1993 | case 7: 1994 | term->c.attr.mode |= ATTR_REVERSE; 1995 | break; 1996 | case 21: 1997 | case 22: 1998 | term->c.attr.mode &= ~ATTR_BOLD; 1999 | break; 2000 | case 23: 2001 | term->c.attr.mode &= ~ATTR_ITALIC; 2002 | break; 2003 | case 24: 2004 | term->c.attr.mode &= ~ATTR_UNDERLINE; 2005 | break; 2006 | case 25: 2007 | case 26: 2008 | term->c.attr.mode &= ~ATTR_BLINK; 2009 | break; 2010 | case 27: 2011 | term->c.attr.mode &= ~ATTR_REVERSE; 2012 | break; 2013 | case 38: 2014 | if(i + 2 < l && attr[i + 1] == 5) { 2015 | i += 2; 2016 | if(BETWEEN(attr[i], 0, 255)) { 2017 | term->c.attr.fg = attr[i]; 2018 | } else { 2019 | fprintf(stderr, 2020 | "erresc: bad fgcolor %d\n", 2021 | attr[i]); 2022 | } 2023 | } else { 2024 | fprintf(stderr, 2025 | "erresc(38): gfx attr %d unknown\n", 2026 | attr[i]); 2027 | } 2028 | break; 2029 | case 39: 2030 | term->c.attr.fg = defaultfg; 2031 | break; 2032 | case 48: 2033 | if(i + 2 < l && attr[i + 1] == 5) { 2034 | i += 2; 2035 | if(BETWEEN(attr[i], 0, 255)) { 2036 | term->c.attr.bg = attr[i]; 2037 | } else { 2038 | fprintf(stderr, 2039 | "erresc: bad bgcolor %d\n", 2040 | attr[i]); 2041 | } 2042 | } else { 2043 | fprintf(stderr, 2044 | "erresc(48): gfx attr %d unknown\n", 2045 | attr[i]); 2046 | } 2047 | break; 2048 | case 49: 2049 | term->c.attr.bg = defaultbg; 2050 | break; 2051 | default: 2052 | if(BETWEEN(attr[i], 30, 37)) { 2053 | term->c.attr.fg = attr[i] - 30; 2054 | } else if(BETWEEN(attr[i], 40, 47)) { 2055 | term->c.attr.bg = attr[i] - 40; 2056 | } else if(BETWEEN(attr[i], 90, 97)) { 2057 | term->c.attr.fg = attr[i] - 90 + 8; 2058 | } else if(BETWEEN(attr[i], 100, 107)) { 2059 | term->c.attr.bg = attr[i] - 100 + 8; 2060 | } else { 2061 | fprintf(stderr, 2062 | "erresc(default): gfx attr %d unknown\n", 2063 | attr[i]), csidump(); 2064 | } 2065 | break; 2066 | } 2067 | } 2068 | } 2069 | 2070 | void 2071 | tsetscroll(Term *term, int t, int b) { 2072 | int temp; 2073 | 2074 | LIMIT(t, 0, term->row-1); 2075 | LIMIT(b, 0, term->row-1); 2076 | if(t > b) { 2077 | temp = t; 2078 | t = b; 2079 | b = temp; 2080 | } 2081 | term->top = t; 2082 | term->bot = b; 2083 | } 2084 | 2085 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 2086 | 2087 | void 2088 | tsetmode(Term *term, bool priv, bool set, int *args, int narg) { 2089 | int *lim, mode; 2090 | bool alt; 2091 | 2092 | for(lim = args + narg; args < lim; ++args) { 2093 | if(priv) { 2094 | switch(*args) { 2095 | break; 2096 | case 1: /* DECCKM -- Cursor key */ 2097 | MODBIT(term->mode, set, MODE_APPCURSOR); 2098 | break; 2099 | case 5: /* DECSCNM -- Reverse video */ 2100 | mode = term->mode; 2101 | MODBIT(term->mode, set, MODE_REVERSE); 2102 | if(mode != term->mode) 2103 | redraw(REDRAW_TIMEOUT); 2104 | break; 2105 | case 6: /* DECOM -- Origin */ 2106 | MODBIT(term->c.state, set, CURSOR_ORIGIN); 2107 | tmoveato(term, 0, 0); 2108 | break; 2109 | case 7: /* DECAWM -- Auto wrap */ 2110 | MODBIT(term->mode, set, MODE_WRAP); 2111 | break; 2112 | case 0: /* Error (IGNORED) */ 2113 | case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 2114 | case 3: /* DECCOLM -- Column (IGNORED) */ 2115 | case 4: /* DECSCLM -- Scroll (IGNORED) */ 2116 | case 8: /* DECARM -- Auto repeat (IGNORED) */ 2117 | case 18: /* DECPFF -- Printer feed (IGNORED) */ 2118 | case 19: /* DECPEX -- Printer extent (IGNORED) */ 2119 | case 42: /* DECNRCM -- National characters (IGNORED) */ 2120 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 2121 | break; 2122 | case 25: /* DECTCEM -- Text Cursor Enable Mode */ 2123 | MODBIT(term->mode, !set, MODE_HIDE); 2124 | break; 2125 | case 1000: /* 1000,1002: enable xterm mouse report */ 2126 | MODBIT(term->mode, set, MODE_MOUSEBTN); 2127 | MODBIT(term->mode, 0, MODE_MOUSEMOTION); 2128 | break; 2129 | case 1002: 2130 | MODBIT(term->mode, set, MODE_MOUSEMOTION); 2131 | MODBIT(term->mode, 0, MODE_MOUSEBTN); 2132 | break; 2133 | case 1006: 2134 | MODBIT(term->mode, set, MODE_MOUSESGR); 2135 | break; 2136 | case 1034: 2137 | MODBIT(term->mode, set, MODE_8BIT); 2138 | break; 2139 | case 1003: /* all motion */ 2140 | MODBIT(term->mode, set, MODE_MOUSE); 2141 | break; 2142 | case 1049: /* = 1047 and 1048 */ 2143 | case 47: 2144 | case 1047: 2145 | if (!allowaltscreen) 2146 | break; 2147 | 2148 | alt = IS_SET(term, MODE_ALTSCREEN); 2149 | if(alt) { 2150 | tclearregion(term, 0, 0, term->col-1, 2151 | term->row-1); 2152 | } 2153 | if(set ^ alt) /* set is always 1 or 0 */ 2154 | tswapscreen(term); 2155 | if(*args != 1049) 2156 | break; 2157 | /* FALLTRU */ 2158 | case 1048: 2159 | tcursor(term, (set) ? CURSOR_SAVE : CURSOR_LOAD); 2160 | break; 2161 | default: 2162 | fprintf(stderr, 2163 | "erresc: unknown private set/reset mode %d\n", 2164 | *args); 2165 | break; 2166 | } 2167 | } else { 2168 | switch(*args) { 2169 | case 0: /* Error (IGNORED) */ 2170 | break; 2171 | case 2: /* KAM -- keyboard action */ 2172 | MODBIT(term->mode, set, MODE_KBDLOCK); 2173 | break; 2174 | case 4: /* IRM -- Insertion-replacement */ 2175 | MODBIT(term->mode, set, MODE_INSERT); 2176 | break; 2177 | case 12: /* SRM -- Send/Receive */ 2178 | MODBIT(term->mode, !set, MODE_ECHO); 2179 | break; 2180 | case 20: /* LNM -- Linefeed/new line */ 2181 | MODBIT(term->mode, set, MODE_CRLF); 2182 | break; 2183 | default: 2184 | fprintf(stderr, 2185 | "erresc: unknown set/reset mode %d\n", 2186 | *args); 2187 | break; 2188 | } 2189 | } 2190 | } 2191 | } 2192 | #undef MODBIT 2193 | 2194 | 2195 | void 2196 | csihandle(Term *term) { 2197 | /* temporary workaround */ 2198 | if (csiescseq.prefix && csiescseq.mode != 'c' && csiescseq.mode != 'n') { 2199 | goto unknown; 2200 | } 2201 | switch(csiescseq.mode) { 2202 | default: 2203 | unknown: 2204 | fprintf(stderr, "erresc: unknown csi "); 2205 | csidump(); 2206 | /* die(""); */ 2207 | break; 2208 | case '@': /* ICH -- Insert blank char */ 2209 | DEFAULT(csiescseq.arg[0], 1); 2210 | tinsertblank(term, csiescseq.arg[0]); 2211 | break; 2212 | case 'A': /* CUU -- Cursor Up */ 2213 | DEFAULT(csiescseq.arg[0], 1); 2214 | tmoveto(term, term->c.x, term->c.y-csiescseq.arg[0]); 2215 | break; 2216 | case 'B': /* CUD -- Cursor Down */ 2217 | case 'e': /* VPR --Cursor Down */ 2218 | DEFAULT(csiescseq.arg[0], 1); 2219 | tmoveto(term, term->c.x, term->c.y+csiescseq.arg[0]); 2220 | break; 2221 | case 'c': /* DA -- Device Attributes */ 2222 | if (csiescseq.prefix == '>') { 2223 | ttywrite(term, "\x1b[>0;276;0c", 11); 2224 | } else if (csiescseq.arg[0] == 0) { 2225 | ttywrite(term, VT102ID, sizeof(VT102ID) - 1); 2226 | } else { 2227 | goto unknown; 2228 | } 2229 | break; 2230 | case 'C': /* CUF -- Cursor Forward */ 2231 | case 'a': /* HPR -- Cursor Forward */ 2232 | DEFAULT(csiescseq.arg[0], 1); 2233 | tmoveto(term, term->c.x+csiescseq.arg[0], term->c.y); 2234 | break; 2235 | case 'D': /* CUB -- Cursor Backward */ 2236 | DEFAULT(csiescseq.arg[0], 1); 2237 | tmoveto(term, term->c.x-csiescseq.arg[0], term->c.y); 2238 | break; 2239 | case 'E': /* CNL -- Cursor Down and first col */ 2240 | DEFAULT(csiescseq.arg[0], 1); 2241 | tmoveto(term, 0, term->c.y+csiescseq.arg[0]); 2242 | break; 2243 | case 'F': /* CPL -- Cursor Up and first col */ 2244 | DEFAULT(csiescseq.arg[0], 1); 2245 | tmoveto(term, 0, term->c.y-csiescseq.arg[0]); 2246 | break; 2247 | case 'g': /* TBC -- Tabulation clear */ 2248 | switch(csiescseq.arg[0]) { 2249 | case 0: /* clear current tab stop */ 2250 | term->tabs[term->c.x] = 0; 2251 | break; 2252 | case 3: /* clear all the tabs */ 2253 | memset(term->tabs, 0, term->col * sizeof(*term->tabs)); 2254 | break; 2255 | default: 2256 | goto unknown; 2257 | } 2258 | break; 2259 | case 'G': /* CHA -- Move to */ 2260 | case '`': /* HPA */ 2261 | DEFAULT(csiescseq.arg[0], 1); 2262 | tmoveto(term, csiescseq.arg[0]-1, term->c.y); 2263 | break; 2264 | case 'H': /* CUP -- Move to */ 2265 | case 'f': /* HVP */ 2266 | DEFAULT(csiescseq.arg[0], 1); 2267 | DEFAULT(csiescseq.arg[1], 1); 2268 | tmoveato(term, csiescseq.arg[1]-1, csiescseq.arg[0]-1); 2269 | break; 2270 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 2271 | DEFAULT(csiescseq.arg[0], 1); 2272 | while(csiescseq.arg[0]--) 2273 | tputtab(term, 1); 2274 | break; 2275 | case 'J': /* ED -- Clear screen */ 2276 | sel.bx = -1; 2277 | switch(csiescseq.arg[0]) { 2278 | case 0: /* below */ 2279 | tclearregion(term, term->c.x, term->c.y, term->col-1, term->c.y); 2280 | if(term->c.y < term->row-1) { 2281 | tclearregion(term, 0, term->c.y+1, term->col-1, 2282 | term->row-1); 2283 | } 2284 | break; 2285 | case 1: /* above */ 2286 | if(term->c.y > 1) 2287 | tclearregion(term, 0, 0, term->col-1, term->c.y-1); 2288 | tclearregion(term, 0, term->c.y, term->c.x, term->c.y); 2289 | break; 2290 | case 2: /* all */ 2291 | tclearregion(term, 0, 0, term->col-1, term->row-1); 2292 | break; 2293 | default: 2294 | goto unknown; 2295 | } 2296 | break; 2297 | case 'K': /* EL -- Clear line */ 2298 | switch(csiescseq.arg[0]) { 2299 | case 0: /* right */ 2300 | tclearregion(term, term->c.x, term->c.y, term->col-1, 2301 | term->c.y); 2302 | break; 2303 | case 1: /* left */ 2304 | tclearregion(term, 0, term->c.y, term->c.x, term->c.y); 2305 | break; 2306 | case 2: /* all */ 2307 | tclearregion(term, 0, term->c.y, term->col-1, term->c.y); 2308 | break; 2309 | } 2310 | break; 2311 | case 'S': /* SU -- Scroll line up */ 2312 | DEFAULT(csiescseq.arg[0], 1); 2313 | tscrollup(term, term->top, csiescseq.arg[0]); 2314 | break; 2315 | case 'T': /* SD -- Scroll line down */ 2316 | DEFAULT(csiescseq.arg[0], 1); 2317 | tscrolldown(term, term->top, csiescseq.arg[0]); 2318 | break; 2319 | case 'L': /* IL -- Insert blank lines */ 2320 | DEFAULT(csiescseq.arg[0], 1); 2321 | tinsertblankline(term, csiescseq.arg[0]); 2322 | break; 2323 | case 'l': /* RM -- Reset Mode */ 2324 | tsetmode(term, csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 2325 | break; 2326 | case 'M': /* DL -- Delete lines */ 2327 | DEFAULT(csiescseq.arg[0], 1); 2328 | tdeleteline(term, csiescseq.arg[0]); 2329 | break; 2330 | case 'X': /* ECH -- Erase char */ 2331 | DEFAULT(csiescseq.arg[0], 1); 2332 | tclearregion(term, term->c.x, term->c.y, 2333 | term->c.x + csiescseq.arg[0] - 1, term->c.y); 2334 | break; 2335 | case 'P': /* DCH -- Delete char */ 2336 | DEFAULT(csiescseq.arg[0], 1); 2337 | tdeletechar(term, csiescseq.arg[0]); 2338 | break; 2339 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 2340 | DEFAULT(csiescseq.arg[0], 1); 2341 | while(csiescseq.arg[0]--) 2342 | tputtab(term, 0); 2343 | break; 2344 | case 'd': /* VPA -- Move to */ 2345 | DEFAULT(csiescseq.arg[0], 1); 2346 | tmoveato(term, term->c.x, csiescseq.arg[0]-1); 2347 | break; 2348 | case 'h': /* SM -- Set terminal mode */ 2349 | tsetmode(term, csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 2350 | break; 2351 | case 'm': /* SGR -- Terminal attribute (color) */ 2352 | tsetattr(term, csiescseq.arg, csiescseq.narg); 2353 | break; 2354 | case 'r': /* DECSTBM -- Set Scrolling Region */ 2355 | if(csiescseq.priv) { 2356 | goto unknown; 2357 | } else { 2358 | DEFAULT(csiescseq.arg[0], 1); 2359 | DEFAULT(csiescseq.arg[1], term->row); 2360 | tsetscroll(term, csiescseq.arg[0]-1, csiescseq.arg[1]-1); 2361 | tmoveato(term, 0, 0); 2362 | } 2363 | break; 2364 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 2365 | tcursor(term, CURSOR_SAVE); 2366 | break; 2367 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 2368 | tcursor(term, CURSOR_LOAD); 2369 | break; 2370 | case 'n': /* DSR -- Device Status Report */ 2371 | if (csiescseq.arg[0] == 6) { 2372 | char buf[30]; 2373 | int len = snprintf(buf, sizeof(buf), "\x1b[?%d;%dR", term->c.y + 1, term->c.x + 1); 2374 | ttywrite(term, buf, len); 2375 | } else { 2376 | goto unknown; 2377 | } 2378 | break; 2379 | } 2380 | } 2381 | 2382 | void 2383 | csidump(void) { 2384 | int i; 2385 | uint c; 2386 | 2387 | printf("ESC["); 2388 | for(i = 0; i < csiescseq.len; i++) { 2389 | c = csiescseq.buf[i] & 0xff; 2390 | if(isprint(c)) { 2391 | putchar(c); 2392 | } else if(c == '\n') { 2393 | printf("(\\n)"); 2394 | } else if(c == '\r') { 2395 | printf("(\\r)"); 2396 | } else if(c == 0x1b) { 2397 | printf("(\\e)"); 2398 | } else { 2399 | printf("(%02x)", c); 2400 | } 2401 | } 2402 | putchar('\n'); 2403 | } 2404 | 2405 | void 2406 | csireset(void) { 2407 | memset(&csiescseq, 0, sizeof(csiescseq)); 2408 | } 2409 | 2410 | void 2411 | strhandle(Term *term) { 2412 | char *p = NULL; 2413 | int i, j, narg; 2414 | 2415 | strparse(); 2416 | narg = strescseq.narg; 2417 | 2418 | switch(strescseq.type) { 2419 | case ']': /* OSC -- Operating System Command */ 2420 | switch(i = atoi(strescseq.args[0])) { 2421 | case 0: 2422 | case 1: 2423 | case 2: 2424 | if(narg > 1) 2425 | xsettitle(strescseq.args[1]); 2426 | break; 2427 | case 4: /* color set */ 2428 | if(narg < 3) 2429 | break; 2430 | p = strescseq.args[2]; 2431 | /* fall through */ 2432 | case 104: /* color reset, here p = NULL */ 2433 | j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2434 | if (!xsetcolorname(j, p)) { 2435 | fprintf(stderr, "erresc: invalid color %s\n", p); 2436 | } else { 2437 | /* 2438 | * TODO if defaultbg color is changed, borders 2439 | * are dirty 2440 | */ 2441 | if (term == focused_term) redraw(0); 2442 | } 2443 | break; 2444 | default: 2445 | fprintf(stderr, "erresc: unknown str "); 2446 | strdump(); 2447 | break; 2448 | } 2449 | break; 2450 | case 'k': /* old title set compatibility */ 2451 | xsettitle(strescseq.args[0]); 2452 | break; 2453 | case 'P': { /* DSC -- Device Control String */ 2454 | /* NOTE: Just send vim some strings it wants to see. */ 2455 | /* receive: ^[P+q2332^[\ */ 2456 | /* reply: ^[P0+r2332^[\ */ 2457 | /* reply: ^[P{valid:0|1}{sign:*|+}r{str}^[\ */ 2458 | char buf[30]; 2459 | int valid = 0; 2460 | int len = snprintf( 2461 | buf, sizeof(buf), "\x1bP%d%cr%s\x1b\\", 2462 | valid, strescseq.buf[0], strescseq.buf + 2); 2463 | ttywrite(term, buf, len); 2464 | break; 2465 | } 2466 | case '_': /* APC -- Application Program Command */ 2467 | case '^': /* PM -- Privacy Message */ 2468 | default: 2469 | fprintf(stderr, "erresc: unknown str "); 2470 | strdump(); 2471 | /* die(""); */ 2472 | break; 2473 | } 2474 | } 2475 | 2476 | void 2477 | strparse(void) { 2478 | char *p = strescseq.buf; 2479 | 2480 | strescseq.narg = 0; 2481 | strescseq.buf[strescseq.len] = '\0'; 2482 | while(p && strescseq.narg < STR_ARG_SIZ) 2483 | strescseq.args[strescseq.narg++] = strsep(&p, ";"); 2484 | } 2485 | 2486 | void 2487 | strdump(void) { 2488 | int i; 2489 | uint c; 2490 | 2491 | printf("ESC%c", strescseq.type); 2492 | for(i = 0; i < strescseq.len; i++) { 2493 | c = strescseq.buf[i] & 0xff; 2494 | if(c == '\0') { 2495 | return; 2496 | } else if(isprint(c)) { 2497 | putchar(c); 2498 | } else if(c == '\n') { 2499 | printf("(\\n)"); 2500 | } else if(c == '\r') { 2501 | printf("(\\r)"); 2502 | } else if(c == 0x1b) { 2503 | printf("(\\e)"); 2504 | } else { 2505 | printf("(%02x)", c); 2506 | } 2507 | } 2508 | printf("ESC\\\n"); 2509 | } 2510 | 2511 | void 2512 | strreset(void) { 2513 | memset(&strescseq, 0, sizeof(strescseq)); 2514 | } 2515 | 2516 | void 2517 | tputtab(Term *term, bool forward) { 2518 | uint x = term->c.x; 2519 | 2520 | if(forward) { 2521 | if(x == term->col) 2522 | return; 2523 | for(++x; x < term->col && !term->tabs[x]; ++x) 2524 | /* nothing */ ; 2525 | } else { 2526 | if(x == 0) 2527 | return; 2528 | for(--x; x > 0 && !term->tabs[x]; --x) 2529 | /* nothing */ ; 2530 | } 2531 | tmoveto(term, x, term->c.y); 2532 | } 2533 | 2534 | void 2535 | techo(Term *term, char *buf, int len) { 2536 | for(; len > 0; buf++, len--) { 2537 | char c = *buf; 2538 | 2539 | if(c == '\033') { /* escape */ 2540 | tputc(term, "^", 1); 2541 | tputc(term, "[", 1); 2542 | } else if(c < '\x20') { /* control code */ 2543 | if(c != '\n' && c != '\r' && c != '\t') { 2544 | c |= '\x40'; 2545 | tputc(term, "^", 1); 2546 | } 2547 | tputc(term, &c, 1); 2548 | } else { 2549 | break; 2550 | } 2551 | } 2552 | if(len) 2553 | tputc(term, buf, len); 2554 | } 2555 | 2556 | void 2557 | tputc(Term *term, char *c, int len) { 2558 | uchar ascii = *c; 2559 | bool control = ascii < '\x20' || ascii == 0177; 2560 | 2561 | if(iofd != -1) { 2562 | if(xwrite(iofd, c, len) < 0) { 2563 | fprintf(stderr, "Error writing in %s:%s\n", 2564 | opt_io, strerror(errno)); 2565 | close(iofd); 2566 | iofd = -1; 2567 | } 2568 | } 2569 | 2570 | /* 2571 | * STR sequences must be checked before anything else 2572 | * because it can use some control codes as part of the sequence. 2573 | */ 2574 | if(term->esc & ESC_STR) { 2575 | switch(ascii) { 2576 | case '\033': 2577 | term->esc = ESC_START | ESC_STR_END; 2578 | break; 2579 | case '\a': /* backwards compatibility to xterm */ 2580 | term->esc = 0; 2581 | strhandle(term); 2582 | break; 2583 | default: 2584 | if(strescseq.len + len < sizeof(strescseq.buf) - 1) { 2585 | memmove(&strescseq.buf[strescseq.len], c, len); 2586 | strescseq.len += len; 2587 | } else { 2588 | /* 2589 | * Here is a bug in terminals. If the user never sends 2590 | * some code to stop the str or esc command, then st 2591 | * will stop responding. But this is better than 2592 | * silently failing with unknown characters. At least 2593 | * then users will report back. 2594 | * 2595 | * In the case users ever get fixed, here is the code: 2596 | */ 2597 | /* 2598 | * term->esc = 0; 2599 | * strhandle(term); 2600 | */ 2601 | } 2602 | } 2603 | return; 2604 | } 2605 | 2606 | /* 2607 | * Actions of control codes must be performed as soon they arrive 2608 | * because they can be embedded inside a control sequence, and 2609 | * they must not cause conflicts with sequences. 2610 | */ 2611 | if(control) { 2612 | switch(ascii) { 2613 | case '\t': /* HT */ 2614 | tputtab(term, 1); 2615 | return; 2616 | case '\b': /* BS */ 2617 | tmoveto(term, term->c.x-1, term->c.y); 2618 | return; 2619 | case '\r': /* CR */ 2620 | tmoveto(term, 0, term->c.y); 2621 | return; 2622 | case '\f': /* LF */ 2623 | case '\v': /* VT */ 2624 | case '\n': /* LF */ 2625 | /* go to first col if the mode is set */ 2626 | tnewline(term, IS_SET(term, MODE_CRLF)); 2627 | return; 2628 | case '\a': /* BEL */ 2629 | if(!(xw.state & WIN_FOCUSED)) 2630 | xseturgency(1); 2631 | return; 2632 | case '\033': /* ESC */ 2633 | csireset(); 2634 | term->esc = ESC_START; 2635 | return; 2636 | case '\016': /* SO */ 2637 | case '\017': /* SI */ 2638 | /* 2639 | * Different charsets are hard to handle. Applications 2640 | * should use the right alt charset escapes for the 2641 | * only reason they still exist: line drawing. The 2642 | * rest is incompatible history st should not support. 2643 | */ 2644 | return; 2645 | case '\032': /* SUB */ 2646 | case '\030': /* CAN */ 2647 | csireset(); 2648 | return; 2649 | case '\005': /* ENQ (IGNORED) */ 2650 | case '\000': /* NUL (IGNORED) */ 2651 | case '\021': /* XON (IGNORED) */ 2652 | case '\023': /* XOFF (IGNORED) */ 2653 | case 0177: /* DEL (IGNORED) */ 2654 | return; 2655 | } 2656 | } else if(term->esc & ESC_START) { 2657 | if(term->esc & ESC_CSI) { 2658 | csiescseq.buf[csiescseq.len++] = ascii; 2659 | if(BETWEEN(ascii, 0x40, 0x7E) 2660 | || csiescseq.len >= \ 2661 | sizeof(csiescseq.buf)-1) { 2662 | term->esc = 0; 2663 | csiparse(); 2664 | csihandle(term); 2665 | } 2666 | } else if(term->esc & ESC_STR_END) { 2667 | term->esc = 0; 2668 | if(ascii == '\\') 2669 | strhandle(term); 2670 | } else if(term->esc & ESC_ALTCHARSET) { 2671 | switch(ascii) { 2672 | case '0': /* Line drawing set */ 2673 | term->c.attr.mode |= ATTR_GFX; 2674 | break; 2675 | case 'B': /* USASCII */ 2676 | term->c.attr.mode &= ~ATTR_GFX; 2677 | break; 2678 | case 'A': /* UK (IGNORED) */ 2679 | case '<': /* multinational charset (IGNORED) */ 2680 | case '5': /* Finnish (IGNORED) */ 2681 | case 'C': /* Finnish (IGNORED) */ 2682 | case 'K': /* German (IGNORED) */ 2683 | break; 2684 | default: 2685 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2686 | } 2687 | term->esc = 0; 2688 | } else if(term->esc & ESC_TEST) { 2689 | if(ascii == '8') { /* DEC screen alignment test. */ 2690 | char E[UTF_SIZ] = "E"; 2691 | int x, y; 2692 | 2693 | for(x = 0; x < term->col; ++x) { 2694 | for(y = 0; y < term->row; ++y) 2695 | tsetchar(term, E, &term->c.attr, x, y); 2696 | } 2697 | } 2698 | term->esc = 0; 2699 | } else { 2700 | switch(ascii) { 2701 | case '[': 2702 | term->esc |= ESC_CSI; 2703 | break; 2704 | case '#': 2705 | term->esc |= ESC_TEST; 2706 | break; 2707 | case 'P': /* DCS -- Device Control String */ 2708 | case '_': /* APC -- Application Program Command */ 2709 | case '^': /* PM -- Privacy Message */ 2710 | case ']': /* OSC -- Operating System Command */ 2711 | case 'k': /* old title set compatibility */ 2712 | strreset(); 2713 | strescseq.type = ascii; 2714 | term->esc |= ESC_STR; 2715 | break; 2716 | case '(': /* set primary charset G0 */ 2717 | term->esc |= ESC_ALTCHARSET; 2718 | break; 2719 | case ')': /* set secondary charset G1 (IGNORED) */ 2720 | case '*': /* set tertiary charset G2 (IGNORED) */ 2721 | case '+': /* set quaternary charset G3 (IGNORED) */ 2722 | term->esc = 0; 2723 | break; 2724 | case 'D': /* IND -- Linefeed */ 2725 | if(term->c.y == term->bot) { 2726 | tscrollup(term, term->top, 1); 2727 | } else { 2728 | tmoveto(term, term->c.x, term->c.y+1); 2729 | } 2730 | term->esc = 0; 2731 | break; 2732 | case 'E': /* NEL -- Next line */ 2733 | tnewline(term, 1); /* always go to first col */ 2734 | term->esc = 0; 2735 | break; 2736 | case 'H': /* HTS -- Horizontal tab stop */ 2737 | term->tabs[term->c.x] = 1; 2738 | term->esc = 0; 2739 | break; 2740 | case 'M': /* RI -- Reverse index */ 2741 | if(term->c.y == term->top) { 2742 | tscrolldown(term, term->top, 1); 2743 | } else { 2744 | tmoveto(term, term->c.x, term->c.y-1); 2745 | } 2746 | term->esc = 0; 2747 | break; 2748 | case 'Z': /* DECID -- Identify Terminal */ 2749 | ttywrite(term, VT102ID, sizeof(VT102ID) - 1); 2750 | term->esc = 0; 2751 | break; 2752 | case 'c': /* RIS -- Reset to inital state */ 2753 | treset(term); 2754 | term->esc = 0; 2755 | xresettitle(); 2756 | break; 2757 | case '=': /* DECPAM -- Application keypad */ 2758 | term->mode |= MODE_APPKEYPAD; 2759 | term->esc = 0; 2760 | break; 2761 | case '>': /* DECPNM -- Normal keypad */ 2762 | term->mode &= ~MODE_APPKEYPAD; 2763 | term->esc = 0; 2764 | break; 2765 | case '7': /* DECSC -- Save Cursor */ 2766 | tcursor(term, CURSOR_SAVE); 2767 | term->esc = 0; 2768 | break; 2769 | case '8': /* DECRC -- Restore Cursor */ 2770 | tcursor(term, CURSOR_LOAD); 2771 | term->esc = 0; 2772 | break; 2773 | case '\\': /* ST -- Stop */ 2774 | term->esc = 0; 2775 | break; 2776 | default: 2777 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2778 | (uchar) ascii, isprint(ascii)? ascii:'.'); 2779 | term->esc = 0; 2780 | } 2781 | } 2782 | /* 2783 | * All characters which form part of a sequence are not 2784 | * printed 2785 | */ 2786 | return; 2787 | } 2788 | /* 2789 | * Display control codes only if we are in graphic mode 2790 | */ 2791 | if(control && !(term->c.attr.mode & ATTR_GFX)) 2792 | return; 2793 | if(sel.bx != -1 && BETWEEN(term->c.y, sel.by, sel.ey)) 2794 | sel.bx = -1; 2795 | if(IS_SET(term, MODE_WRAP) && (term->c.state & CURSOR_WRAPNEXT)) { 2796 | term->line[term->c.y][term->c.x].mode |= ATTR_WRAP; 2797 | tnewline(term, 1); 2798 | } 2799 | 2800 | if(IS_SET(term, MODE_INSERT) && term->c.x+1 < term->col) { 2801 | memmove(&term->line[term->c.y][term->c.x+1], 2802 | &term->line[term->c.y][term->c.x], 2803 | (term->col - term->c.x - 1) * sizeof(Glyph)); 2804 | } 2805 | 2806 | tsetchar(term, c, &term->c.attr, term->c.x, term->c.y); 2807 | if(term->c.x+1 < term->col) { 2808 | tmoveto(term, term->c.x+1, term->c.y); 2809 | } else { 2810 | term->c.state |= CURSOR_WRAPNEXT; 2811 | } 2812 | } 2813 | 2814 | int 2815 | tresize(Term *term, int col, int row) { 2816 | int i; 2817 | int minrow = MIN(row, term->row); 2818 | int mincol = MIN(col, term->col); 2819 | int slide = term->c.y - row + 1; 2820 | bool *bp; 2821 | Line *orig; 2822 | 2823 | if(col < 1 || row < 1) 2824 | return 0; 2825 | 2826 | /* free unneeded rows */ 2827 | i = 0; 2828 | if(slide > 0) { 2829 | /* 2830 | * slide screen to keep cursor where we expect it - 2831 | * tscrollup would work here, but we can optimize to 2832 | * memmove because we're freeing the earlier lines 2833 | */ 2834 | for(/* i = 0 */; i < slide; i++) { 2835 | free(term->line[i]); 2836 | free(term->alt[i]); 2837 | free(term->last_line[i]); 2838 | } 2839 | memmove(term->line, term->line + slide, row * sizeof(Line)); 2840 | memmove(term->alt, term->alt + slide, row * sizeof(Line)); 2841 | memmove(term->last_line, term->last_line + slide, row * sizeof(Line)); 2842 | } 2843 | for(i += row; i < term->row; i++) { 2844 | free(term->line[i]); 2845 | free(term->alt[i]); 2846 | free(term->last_line[i]); 2847 | } 2848 | 2849 | /* resize to new height */ 2850 | term->line = xrealloc(term->line, row * sizeof(Line)); 2851 | term->alt = xrealloc(term->alt, row * sizeof(Line)); 2852 | term->dirty = xrealloc(term->dirty, row * sizeof(*term->dirty)); 2853 | term->tabs = xrealloc(term->tabs, col * sizeof(*term->tabs)); 2854 | term->last_line = xrealloc(term->last_line, row * sizeof(Line)); 2855 | 2856 | /* resize the scrollback */ 2857 | if (!term->sb) { 2858 | term->sb = xmalloc(scrollback * sizeof(Line)); 2859 | memset(term->sb, 0, scrollback * sizeof(Line)); 2860 | } 2861 | // if (term->col != col) { 2862 | for(i = 0; i < scrollback; i++) { 2863 | term->sb[i] = xrealloc(term->sb[i], col * sizeof(Glyph)); 2864 | } 2865 | 2866 | /* resize each row to new width, zero-pad if needed */ 2867 | for(i = 0; i < minrow; i++) { 2868 | term->dirty[i] = 1; 2869 | term->line[i] = xrealloc(term->line[i], col * sizeof(Glyph)); 2870 | term->alt[i] = xrealloc(term->alt[i], col * sizeof(Glyph)); 2871 | term->last_line[i] = xrealloc(term->last_line[i], col * sizeof(Glyph)); 2872 | } 2873 | 2874 | /* allocate any new rows */ 2875 | for(/* i == minrow */; i < row; i++) { 2876 | term->dirty[i] = 1; 2877 | term->line[i] = xcalloc(col, sizeof(Glyph)); 2878 | term->alt [i] = xcalloc(col, sizeof(Glyph)); 2879 | term->last_line[i] = xcalloc(col, sizeof(Glyph)); 2880 | } 2881 | if(col > term->col) { 2882 | bp = term->tabs + term->col; 2883 | 2884 | memset(bp, 0, sizeof(*term->tabs) * (col - term->col)); 2885 | while(--bp > term->tabs && !*bp) 2886 | /* nothing */ ; 2887 | for(bp += tabspaces; bp < term->tabs + col; bp += tabspaces) 2888 | *bp = 1; 2889 | } 2890 | /* update terminal size */ 2891 | term->col = col; 2892 | term->row = row; 2893 | /* reset scrolling region */ 2894 | tsetscroll(term, 0, row-1); 2895 | /* make use of the LIMIT in tmoveto */ 2896 | tmoveto(term, term->c.x, term->c.y); 2897 | /* Clearing both screens */ 2898 | orig = term->line; 2899 | do { 2900 | if(mincol < col && 0 < minrow) { 2901 | tclearregion(term, mincol, 0, col - 1, minrow - 1); 2902 | } 2903 | if(0 < col && minrow < row) { 2904 | tclearregion(term, 0, minrow, col - 1, row - 1); 2905 | } 2906 | tswapscreen(term); 2907 | } while(orig != term->line); 2908 | 2909 | return (slide > 0); 2910 | } 2911 | 2912 | void 2913 | xresize(int col, int row) { 2914 | xw.tw = MAX(1, col * xw.cw); 2915 | xw.th = MAX(1, row * xw.ch); 2916 | 2917 | XFreePixmap(xw.dpy, xw.buf); 2918 | xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, 2919 | DefaultDepth(xw.dpy, xw.scr)); 2920 | XftDrawChange(xw.draw, xw.buf); 2921 | xclear(0, 0, xw.w, xw.h); 2922 | } 2923 | 2924 | static inline ushort 2925 | sixd_to_16bit(int x) { 2926 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; 2927 | } 2928 | 2929 | void 2930 | xloadcols(void) { 2931 | int i, r, g, b; 2932 | XRenderColor color = { .alpha = 0xffff }; 2933 | 2934 | /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */ 2935 | for(i = 0; i < LEN(colorname); i++) { 2936 | if(!colorname[i]) 2937 | continue; 2938 | if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.col[i])) { 2939 | die("Could not allocate color '%s'\n", colorname[i]); 2940 | } 2941 | } 2942 | 2943 | /* load colors [16-255] ; same colors as xterm */ 2944 | for(i = 16, r = 0; r < 6; r++) { 2945 | for(g = 0; g < 6; g++) { 2946 | for(b = 0; b < 6; b++) { 2947 | color.red = sixd_to_16bit(r); 2948 | color.green = sixd_to_16bit(g); 2949 | color.blue = sixd_to_16bit(b); 2950 | if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &dc.col[i])) { 2951 | die("Could not allocate color %d\n", i); 2952 | } 2953 | i++; 2954 | } 2955 | } 2956 | } 2957 | 2958 | for(r = 0; r < 24; r++, i++) { 2959 | color.red = color.green = color.blue = 0x0808 + 0x0a0a * r; 2960 | if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, 2961 | &dc.col[i])) { 2962 | die("Could not allocate color %d\n", i); 2963 | } 2964 | } 2965 | } 2966 | 2967 | int 2968 | xsetcolorname(int x, const char *name) { 2969 | XRenderColor color = { .alpha = 0xffff }; 2970 | Colour colour; 2971 | if (x < 0 || x > LEN(colorname)) 2972 | return -1; 2973 | if(!name) { 2974 | if(16 <= x && x < 16 + 216) { 2975 | int r = (x - 16) / 36, g = ((x - 16) % 36) / 6, b = (x - 16) % 6; 2976 | color.red = sixd_to_16bit(r); 2977 | color.green = sixd_to_16bit(g); 2978 | color.blue = sixd_to_16bit(b); 2979 | if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &colour)) 2980 | return 0; /* something went wrong */ 2981 | dc.col[x] = colour; 2982 | return 1; 2983 | } else if (16 + 216 <= x && x < 256) { 2984 | color.red = color.green = color.blue = 0x0808 + 0x0a0a * (x - (16 + 216)); 2985 | if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &colour)) 2986 | return 0; /* something went wrong */ 2987 | dc.col[x] = colour; 2988 | return 1; 2989 | } else { 2990 | name = colorname[x]; 2991 | } 2992 | } 2993 | if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, &colour)) 2994 | return 0; 2995 | dc.col[x] = colour; 2996 | return 1; 2997 | } 2998 | 2999 | void 3000 | xtermclear(int col1, int row1, int col2, int row2) { 3001 | XftDrawRect(xw.draw, 3002 | &dc.col[IS_SET(focused_term, MODE_REVERSE) ? defaultfg : defaultbg], 3003 | borderpx + col1 * xw.cw, 3004 | borderpx + row1 * xw.ch, 3005 | (col2-col1+1) * xw.cw, 3006 | (row2-row1+1) * xw.ch); 3007 | } 3008 | 3009 | /* 3010 | * Absolute coordinates. 3011 | */ 3012 | void 3013 | xclear(int x1, int y1, int x2, int y2) { 3014 | XftDrawRect(xw.draw, 3015 | &dc.col[IS_SET(focused_term, MODE_REVERSE)? defaultfg : defaultbg], 3016 | x1, y1, x2-x1, y2-y1); 3017 | } 3018 | 3019 | void 3020 | xhints(void) { 3021 | XClassHint class = {opt_class ? opt_class : termname, termname}; 3022 | XWMHints wm = {.flags = InputHint, .input = 1}; 3023 | XSizeHints *sizeh = NULL; 3024 | 3025 | sizeh = XAllocSizeHints(); 3026 | if(xw.isfixed == False) { 3027 | sizeh->flags = PSize | PResizeInc | PBaseSize; 3028 | sizeh->height = xw.h; 3029 | sizeh->width = xw.w; 3030 | sizeh->height_inc = xw.ch; 3031 | sizeh->width_inc = xw.cw; 3032 | sizeh->base_height = 2 * borderpx; 3033 | sizeh->base_width = 2 * borderpx; 3034 | } else { 3035 | sizeh->flags = PMaxSize | PMinSize; 3036 | sizeh->min_width = sizeh->max_width = xw.fw; 3037 | sizeh->min_height = sizeh->max_height = xw.fh; 3038 | } 3039 | 3040 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class); 3041 | XFree(sizeh); 3042 | } 3043 | 3044 | int 3045 | xloadfont(Font *f, FcPattern *pattern) { 3046 | FcPattern *match; 3047 | FcResult result; 3048 | 3049 | match = FcFontMatch(NULL, pattern, &result); 3050 | if(!match) 3051 | return 1; 3052 | 3053 | if(!(f->match = XftFontOpenPattern(xw.dpy, match))) { 3054 | FcPatternDestroy(match); 3055 | return 1; 3056 | } 3057 | 3058 | f->set = NULL; 3059 | f->pattern = FcPatternDuplicate(pattern); 3060 | 3061 | f->ascent = f->match->ascent; 3062 | f->descent = f->match->descent; 3063 | f->lbearing = 0; 3064 | f->rbearing = f->match->max_advance_width; 3065 | 3066 | f->height = f->ascent + f->descent; 3067 | f->width = f->lbearing + f->rbearing; 3068 | 3069 | return 0; 3070 | } 3071 | 3072 | void 3073 | xloadfonts(char *fontstr, int fontsize) { 3074 | FcPattern *pattern; 3075 | FcResult result; 3076 | double fontval; 3077 | 3078 | if(fontstr[0] == '-') { 3079 | pattern = XftXlfdParse(fontstr, False, False); 3080 | } else { 3081 | pattern = FcNameParse((FcChar8 *)fontstr); 3082 | } 3083 | 3084 | if(!pattern) 3085 | die("st: can't open font %s\n", fontstr); 3086 | 3087 | if(fontsize > 0) { 3088 | FcPatternDel(pattern, FC_PIXEL_SIZE); 3089 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 3090 | usedfontsize = fontsize; 3091 | } else { 3092 | result = FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval); 3093 | if(result == FcResultMatch) { 3094 | usedfontsize = (int)fontval; 3095 | } else { 3096 | /* 3097 | * Default font size is 12, if none given. This is to 3098 | * have a known usedfontsize value. 3099 | */ 3100 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 3101 | usedfontsize = 12; 3102 | } 3103 | } 3104 | 3105 | FcConfigSubstitute(0, pattern, FcMatchPattern); 3106 | FcDefaultSubstitute(pattern); 3107 | 3108 | if(xloadfont(&dc.font, pattern)) 3109 | die("st: can't open font %s\n", fontstr); 3110 | 3111 | /* Setting character width and height. */ 3112 | xw.cw = dc.font.width; 3113 | xw.ch = dc.font.height; 3114 | 3115 | FcPatternDel(pattern, FC_SLANT); 3116 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 3117 | if(xloadfont(&dc.ifont, pattern)) 3118 | die("st: can't open font %s\n", fontstr); 3119 | 3120 | FcPatternDel(pattern, FC_WEIGHT); 3121 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 3122 | if(xloadfont(&dc.ibfont, pattern)) 3123 | die("st: can't open font %s\n", fontstr); 3124 | 3125 | FcPatternDel(pattern, FC_SLANT); 3126 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 3127 | if(xloadfont(&dc.bfont, pattern)) 3128 | die("st: can't open font %s\n", fontstr); 3129 | 3130 | FcPatternDestroy(pattern); 3131 | } 3132 | 3133 | int 3134 | xloadfontset(Font *f) { 3135 | FcResult result; 3136 | 3137 | if(!(f->set = FcFontSort(0, f->pattern, FcTrue, 0, &result))) 3138 | return 1; 3139 | return 0; 3140 | } 3141 | 3142 | void 3143 | xunloadfont(Font *f) { 3144 | XftFontClose(xw.dpy, f->match); 3145 | FcPatternDestroy(f->pattern); 3146 | if(f->set) 3147 | FcFontSetDestroy(f->set); 3148 | } 3149 | 3150 | void 3151 | xunloadfonts(void) { 3152 | int i, ip; 3153 | 3154 | /* 3155 | * Free the loaded fonts in the font cache. This is done backwards 3156 | * from the frccur. 3157 | */ 3158 | for(i = 0, ip = frccur; i < frclen; i++, ip--) { 3159 | if(ip < 0) 3160 | ip = LEN(frc) - 1; 3161 | XftFontClose(xw.dpy, frc[ip].font); 3162 | } 3163 | frccur = -1; 3164 | frclen = 0; 3165 | 3166 | xunloadfont(&dc.font); 3167 | xunloadfont(&dc.bfont); 3168 | xunloadfont(&dc.ifont); 3169 | xunloadfont(&dc.ibfont); 3170 | } 3171 | 3172 | void 3173 | xzoom(const Arg *arg) { 3174 | xunloadfonts(); 3175 | xloadfonts(usedfont, usedfontsize + arg->i); 3176 | cresize(0, 0); 3177 | redraw(0); 3178 | } 3179 | 3180 | void 3181 | xinit(void) { 3182 | XSetWindowAttributes attrs; 3183 | XGCValues gcvalues; 3184 | #ifndef USE_BLANK_CURSOR 3185 | Cursor cursor; 3186 | #endif 3187 | Window parent; 3188 | int sw, sh; 3189 | 3190 | if(!(xw.dpy = XOpenDisplay(NULL))) 3191 | die("Can't open display\n"); 3192 | xw.scr = XDefaultScreen(xw.dpy); 3193 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); 3194 | 3195 | /* font */ 3196 | if(!FcInit()) 3197 | die("Could not init fontconfig.\n"); 3198 | 3199 | usedfont = (opt_font == NULL)? font : opt_font; 3200 | xloadfonts(usedfont, 0); 3201 | 3202 | /* colors */ 3203 | xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 3204 | xloadcols(); 3205 | 3206 | /* adjust fixed window geometry */ 3207 | if(xw.isfixed) { 3208 | sw = DisplayWidth(xw.dpy, xw.scr); 3209 | sh = DisplayHeight(xw.dpy, xw.scr); 3210 | if(xw.fx < 0) 3211 | xw.fx = sw + xw.fx - xw.fw - 1; 3212 | if(xw.fy < 0) 3213 | xw.fy = sh + xw.fy - xw.fh - 1; 3214 | 3215 | xw.h = xw.fh; 3216 | xw.w = xw.fw; 3217 | } else { 3218 | /* window - default size */ 3219 | xw.h = 2 * borderpx + focused_term->row * xw.ch; 3220 | xw.w = 2 * borderpx + focused_term->col * xw.cw; 3221 | xw.fx = 0; 3222 | xw.fy = 0; 3223 | } 3224 | 3225 | /* Events */ 3226 | attrs.background_pixel = dc.col[defaultbg].pixel; 3227 | attrs.border_pixel = dc.col[defaultbg].pixel; 3228 | attrs.bit_gravity = NorthWestGravity; 3229 | attrs.event_mask = FocusChangeMask | KeyPressMask 3230 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask 3231 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 3232 | attrs.colormap = xw.cmap; 3233 | 3234 | parent = opt_embed ? strtol(opt_embed, NULL, 0) : \ 3235 | XRootWindow(xw.dpy, xw.scr); 3236 | xw.win = XCreateWindow(xw.dpy, parent, xw.fx, xw.fy, 3237 | xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, 3238 | xw.vis, 3239 | CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask 3240 | | CWColormap, 3241 | &attrs); 3242 | 3243 | memset(&gcvalues, 0, sizeof(gcvalues)); 3244 | gcvalues.graphics_exposures = False; 3245 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 3246 | &gcvalues); 3247 | xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, 3248 | DefaultDepth(xw.dpy, xw.scr)); 3249 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 3250 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h); 3251 | 3252 | /* Xft rendering context */ 3253 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 3254 | 3255 | /* input methods */ 3256 | if((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 3257 | XSetLocaleModifiers("@im=local"); 3258 | if((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 3259 | XSetLocaleModifiers("@im="); 3260 | if((xw.xim = XOpenIM(xw.dpy, 3261 | NULL, NULL, NULL)) == NULL) { 3262 | die("XOpenIM failed. Could not open input" 3263 | " device.\n"); 3264 | } 3265 | } 3266 | } 3267 | xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing 3268 | | XIMStatusNothing, XNClientWindow, xw.win, 3269 | XNFocusWindow, xw.win, NULL); 3270 | if(xw.xic == NULL) 3271 | die("XCreateIC failed. Could not obtain input method.\n"); 3272 | 3273 | /* white cursor, black outline */ 3274 | cursor = XCreateFontCursor(xw.dpy, !notextcursor ? XC_xterm : XC_left_ptr); 3275 | XDefineCursor(xw.dpy, xw.win, cursor); 3276 | if (!notextcursor) { 3277 | XRecolorCursor(xw.dpy, cursor, 3278 | &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff}, 3279 | &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000}); 3280 | } 3281 | 3282 | /* blank cursor */ 3283 | #ifdef USE_BLANK_CURSOR 3284 | XColor black = {0}; 3285 | #undef Font 3286 | //Font f = XLoadFont(xw.dpy, "fixed"); 3287 | #define Font Font_ 3288 | //blank_cursor = XCreateGlyphCursor(xw.dpy, f, f, ' ', ' ', &black, &black); 3289 | //XUnloadFont(xw.dpy, f); 3290 | XFontStruct *f = XLoadQueryFont(xw.dpy, "fixed"); 3291 | blank_cursor = XCreateGlyphCursor(xw.dpy, f->fid, f->fid, 'X', ' ', &black, &black); 3292 | XFreeFont(xw.dpy, f); 3293 | #endif 3294 | 3295 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 3296 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 3297 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 3298 | 3299 | xresettitle(); 3300 | XMapWindow(xw.dpy, xw.win); 3301 | xhints(); 3302 | XSync(xw.dpy, 0); 3303 | } 3304 | 3305 | #ifdef USE_BLANK_CURSOR 3306 | void 3307 | xcursorblank(void) { 3308 | if (hidden_cursor) return; 3309 | hidden_cursor = true; 3310 | XDefineCursor(xw.dpy, xw.win, blank_cursor); 3311 | XFlush(xw.dpy); 3312 | } 3313 | 3314 | void 3315 | xcursorunblank(void) { 3316 | if (!hidden_cursor) return; 3317 | hidden_cursor = false; 3318 | XDefineCursor(xw.dpy, xw.win, cursor); 3319 | } 3320 | #endif 3321 | 3322 | void 3323 | xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { 3324 | int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, 3325 | width = charlen * xw.cw, xp, i; 3326 | int frp, frcflags; 3327 | int u8fl, u8fblen, u8cblen, doesexist; 3328 | char *u8c, *u8fs; 3329 | long u8char; 3330 | Font *font = &dc.font; 3331 | FcResult fcres; 3332 | FcPattern *fcpattern, *fontpattern; 3333 | FcFontSet *fcsets[] = { NULL }; 3334 | FcCharSet *fccharset; 3335 | Colour *fg, *bg, *temp, revfg, revbg; 3336 | XRenderColor colfg, colbg; 3337 | Rectangle r; 3338 | 3339 | frcflags = FRC_NORMAL; 3340 | 3341 | if(base.mode & ATTR_ITALIC) { 3342 | if(base.fg == defaultfg) 3343 | base.fg = defaultitalic; 3344 | font = &dc.ifont; 3345 | frcflags = FRC_ITALIC; 3346 | } else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) { 3347 | if(base.fg == defaultfg) 3348 | base.fg = defaultitalic; 3349 | font = &dc.ibfont; 3350 | frcflags = FRC_ITALICBOLD; 3351 | } else if(base.mode & ATTR_UNDERLINE) { 3352 | if(base.fg == defaultfg) 3353 | base.fg = defaultunderline; 3354 | } 3355 | fg = &dc.col[base.fg]; 3356 | bg = &dc.col[base.bg]; 3357 | 3358 | if(base.mode & ATTR_BOLD) { 3359 | if(BETWEEN(base.fg, 0, 7)) { 3360 | /* basic system colors */ 3361 | fg = &dc.col[base.fg + 8]; 3362 | } else if(BETWEEN(base.fg, 16, 195)) { 3363 | /* 256 colors */ 3364 | fg = &dc.col[base.fg + 36]; 3365 | } else if(BETWEEN(base.fg, 232, 251)) { 3366 | /* greyscale */ 3367 | fg = &dc.col[base.fg + 4]; 3368 | } 3369 | /* 3370 | * Those ranges will not be brightened: 3371 | * 8 - 15 – bright system colors 3372 | * 196 - 231 – highest 256 color cube 3373 | * 252 - 255 – brightest colors in greyscale 3374 | */ 3375 | font = &dc.bfont; 3376 | frcflags = FRC_BOLD; 3377 | } 3378 | 3379 | if(IS_SET(focused_term, MODE_REVERSE)) { 3380 | if(fg == &dc.col[defaultfg]) { 3381 | fg = &dc.col[defaultbg]; 3382 | } else { 3383 | colfg.red = ~fg->color.red; 3384 | colfg.green = ~fg->color.green; 3385 | colfg.blue = ~fg->color.blue; 3386 | colfg.alpha = fg->color.alpha; 3387 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 3388 | fg = &revfg; 3389 | } 3390 | 3391 | if(bg == &dc.col[defaultbg]) { 3392 | bg = &dc.col[defaultfg]; 3393 | } else { 3394 | colbg.red = ~bg->color.red; 3395 | colbg.green = ~bg->color.green; 3396 | colbg.blue = ~bg->color.blue; 3397 | colbg.alpha = bg->color.alpha; 3398 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg); 3399 | bg = &revbg; 3400 | } 3401 | } 3402 | 3403 | if(base.mode & ATTR_REVERSE) { 3404 | temp = fg; 3405 | fg = bg; 3406 | bg = temp; 3407 | } 3408 | 3409 | if(base.mode & ATTR_BLINK && focused_term->mode & MODE_BLINK) 3410 | fg = bg; 3411 | 3412 | /* Intelligent cleaning up of the borders. */ 3413 | if(x == 0) { 3414 | xclear(0, (y == 0)? 0 : winy, borderpx, 3415 | winy + xw.ch + ((y >= focused_term->row-1)? xw.h : 0)); 3416 | } 3417 | if(x + charlen >= focused_term->col) { 3418 | xclear(winx + width, (y == 0)? 0 : winy, xw.w, 3419 | ((y >= focused_term->row-1)? xw.h : (winy + xw.ch))); 3420 | } 3421 | if(y == 0) 3422 | xclear(winx, 0, winx + width, borderpx); 3423 | if(y == focused_term->row-1) 3424 | xclear(winx, winy + xw.ch, winx + width, xw.h); 3425 | 3426 | /* Clean up the region we want to draw to. */ 3427 | XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch); 3428 | 3429 | /* Set the clip region because Xft is sometimes dirty. */ 3430 | r.x = 0; 3431 | r.y = 0; 3432 | r.height = xw.ch; 3433 | r.width = width; 3434 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 3435 | 3436 | for(xp = winx; bytelen > 0;) { 3437 | /* 3438 | * Search for the range in the to be printed string of glyphs 3439 | * that are in the main font. Then print that range. If 3440 | * some glyph is found that is not in the font, do the 3441 | * fallback dance. 3442 | */ 3443 | u8fs = s; 3444 | u8fblen = 0; 3445 | u8fl = 0; 3446 | for(;;) { 3447 | u8c = s; 3448 | u8cblen = utf8decode(s, &u8char); 3449 | s += u8cblen; 3450 | bytelen -= u8cblen; 3451 | 3452 | doesexist = XftCharIndex(xw.dpy, font->match, u8char); 3453 | if(!doesexist || bytelen <= 0) { 3454 | if(bytelen <= 0) { 3455 | if(doesexist) { 3456 | u8fl++; 3457 | u8fblen += u8cblen; 3458 | } 3459 | } 3460 | 3461 | if(u8fl > 0) { 3462 | XftDrawStringUtf8(xw.draw, fg, 3463 | font->match, xp, 3464 | winy + font->ascent, 3465 | (FcChar8 *)u8fs, 3466 | u8fblen); 3467 | #ifdef FORCE_BOLD 3468 | if ((base.mode & ATTR_BOLD) && forcebold) { 3469 | XftDrawStringUtf8(xw.draw, fg, 3470 | font->match, xp + 1, 3471 | winy + font->ascent, 3472 | (FcChar8 *)u8fs, 3473 | u8fblen); 3474 | xp++; 3475 | } 3476 | #endif 3477 | xp += font->width * u8fl; 3478 | 3479 | } 3480 | break; 3481 | } 3482 | 3483 | u8fl++; 3484 | u8fblen += u8cblen; 3485 | } 3486 | if(doesexist) 3487 | break; 3488 | 3489 | frp = frccur; 3490 | /* Search the font cache. */ 3491 | for(i = 0; i < frclen; i++, frp--) { 3492 | if(frp <= 0) 3493 | frp = LEN(frc) - 1; 3494 | 3495 | if(frc[frp].c == u8char 3496 | && frc[frp].flags == frcflags) { 3497 | break; 3498 | } 3499 | } 3500 | 3501 | /* Nothing was found. */ 3502 | if(i >= frclen) { 3503 | if(!font->set) 3504 | xloadfontset(font); 3505 | fcsets[0] = font->set; 3506 | 3507 | /* 3508 | * Nothing was found in the cache. Now use 3509 | * some dozen of Fontconfig calls to get the 3510 | * font for one single character. 3511 | */ 3512 | fcpattern = FcPatternDuplicate(font->pattern); 3513 | fccharset = FcCharSetCreate(); 3514 | 3515 | FcCharSetAddChar(fccharset, u8char); 3516 | FcPatternAddCharSet(fcpattern, FC_CHARSET, 3517 | fccharset); 3518 | FcPatternAddBool(fcpattern, FC_SCALABLE, 3519 | FcTrue); 3520 | 3521 | FcConfigSubstitute(0, fcpattern, 3522 | FcMatchPattern); 3523 | FcDefaultSubstitute(fcpattern); 3524 | 3525 | fontpattern = FcFontSetMatch(0, fcsets, 3526 | FcTrue, fcpattern, &fcres); 3527 | 3528 | /* 3529 | * Overwrite or create the new cache entry. 3530 | */ 3531 | frccur++; 3532 | frclen++; 3533 | if(frccur >= LEN(frc)) 3534 | frccur = 0; 3535 | if(frclen > LEN(frc)) { 3536 | frclen = LEN(frc); 3537 | XftFontClose(xw.dpy, frc[frccur].font); 3538 | } 3539 | 3540 | frc[frccur].font = XftFontOpenPattern(xw.dpy, 3541 | fontpattern); 3542 | frc[frccur].c = u8char; 3543 | frc[frccur].flags = frcflags; 3544 | 3545 | FcPatternDestroy(fcpattern); 3546 | FcCharSetDestroy(fccharset); 3547 | 3548 | frp = frccur; 3549 | } 3550 | 3551 | XftDrawStringUtf8(xw.draw, fg, frc[frp].font, 3552 | xp, winy + frc[frp].font->ascent, 3553 | (FcChar8 *)u8c, u8cblen); 3554 | 3555 | #ifdef FORCE_BOLD 3556 | if ((base.mode & ATTR_BOLD) && forcebold) { 3557 | XftDrawStringUtf8(xw.draw, fg, frc[frp].font, 3558 | xp + 1, winy + frc[frp].font->ascent, 3559 | (FcChar8 *)u8c, u8cblen); 3560 | xp++; 3561 | } 3562 | #endif 3563 | 3564 | xp += font->width; 3565 | } 3566 | 3567 | /* 3568 | XftDrawStringUtf8(xw.draw, fg, font->set, winx, 3569 | winy + font->ascent, (FcChar8 *)s, bytelen); 3570 | */ 3571 | 3572 | if(base.mode & ATTR_UNDERLINE) { 3573 | XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1, 3574 | width, 1); 3575 | } 3576 | 3577 | /* Reset clip to none. */ 3578 | XftDrawSetClip(xw.draw, 0); 3579 | } 3580 | 3581 | void 3582 | xdrawcursor(void) { 3583 | static int oldx = 0, oldy = 0; 3584 | int sl; 3585 | Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs}; 3586 | 3587 | LIMIT(oldx, 0, focused_term->col-1); 3588 | LIMIT(oldy, 0, focused_term->row-1); 3589 | 3590 | // TODO: Use better check to tell whether cursor is one 3591 | // screen even if the ybase is a little bit negative. 3592 | // Also have to draw cursor on the right line in this 3593 | // situation, which makes things complicated. 3594 | // if (focused_term->ybase + (focused_term->row - focused_term->c.y) < 0 && tstate == S_NORMAL) { 3595 | if (focused_term->ybase < 0 && tstate == S_NORMAL) { 3596 | return; 3597 | } 3598 | 3599 | memcpy(g.c, focused_term->line[focused_term->c.y][focused_term->c.x].c, UTF_SIZ); 3600 | 3601 | /* remove the old cursor */ 3602 | sl = utf8size(focused_term->line[oldy][oldx].c); 3603 | xdraws(focused_term->line[oldy][oldx].c, focused_term->line[oldy][oldx], oldx, 3604 | oldy, 1, sl); 3605 | 3606 | /* draw the new one */ 3607 | if(!(IS_SET(focused_term, MODE_HIDE))) { 3608 | if(xw.state & WIN_FOCUSED) { 3609 | if(IS_SET(focused_term, MODE_REVERSE)) { 3610 | g.mode |= ATTR_REVERSE; 3611 | g.fg = defaultcs; 3612 | g.bg = defaultfg; 3613 | } 3614 | 3615 | sl = utf8size(g.c); 3616 | xdraws(g.c, g, focused_term->c.x, focused_term->c.y, 1, sl); 3617 | } else { 3618 | XftDrawRect(xw.draw, &dc.col[defaultcs], 3619 | borderpx + focused_term->c.x * xw.cw, 3620 | borderpx + focused_term->c.y * xw.ch, 3621 | xw.cw - 1, 1); 3622 | XftDrawRect(xw.draw, &dc.col[defaultcs], 3623 | borderpx + focused_term->c.x * xw.cw, 3624 | borderpx + focused_term->c.y * xw.ch, 3625 | 1, xw.ch - 1); 3626 | XftDrawRect(xw.draw, &dc.col[defaultcs], 3627 | borderpx + (focused_term->c.x + 1) * xw.cw - 1, 3628 | borderpx + focused_term->c.y * xw.ch, 3629 | 1, xw.ch - 1); 3630 | XftDrawRect(xw.draw, &dc.col[defaultcs], 3631 | borderpx + focused_term->c.x * xw.cw, 3632 | borderpx + (focused_term->c.y + 1) * xw.ch - 1, 3633 | xw.cw, 1); 3634 | } 3635 | oldx = focused_term->c.x, oldy = focused_term->c.y; 3636 | } 3637 | } 3638 | 3639 | 3640 | void 3641 | xsettitle(char *p) { 3642 | XTextProperty prop; 3643 | 3644 | Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 3645 | &prop); 3646 | XSetWMName(xw.dpy, xw.win, &prop); 3647 | XFree(prop.value); 3648 | } 3649 | 3650 | void 3651 | xresettitle(void) { 3652 | xsettitle(opt_title ? opt_title : "st"); 3653 | } 3654 | 3655 | void 3656 | xmove(int dx, int dy, int sx, int sy, int w, int h) { 3657 | XCopyArea(xw.dpy, xw.buf, xw.buf, dc.gc, 3658 | (sx*xw.cw) + borderpx, (sy*xw.ch) + borderpx, 3659 | (w*xw.cw) + borderpx, (h*xw.ch) + borderpx, 3660 | (dx*xw.cw) + borderpx, (dy*xw.ch) + borderpx); 3661 | } 3662 | 3663 | void 3664 | redraw(int timeout) { 3665 | struct timespec tv = {0, timeout * 1000}; 3666 | 3667 | tfulldirt(focused_term); 3668 | draw(); 3669 | 3670 | if(timeout > 0) { 3671 | nanosleep(&tv, NULL); 3672 | XSync(xw.dpy, False); /* necessary for a good tput flash */ 3673 | } 3674 | } 3675 | 3676 | void 3677 | draw(void) { 3678 | #ifdef OPTIMIZE_RENDER 3679 | focused_term->swapped_lines = false; 3680 | #endif 3681 | drawregion(0, 0, focused_term->col, focused_term->row); 3682 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w, 3683 | xw.h, 0, 0); 3684 | XSetForeground(xw.dpy, dc.gc, 3685 | dc.col[IS_SET(focused_term, MODE_REVERSE)? 3686 | defaultfg : defaultbg].pixel); 3687 | } 3688 | 3689 | void 3690 | drawregion(int x1, int y1, int x2, int y2) { 3691 | int ic, ib, x, y, ox, sl; 3692 | Glyph base, new; 3693 | char buf[DRAW_BUF_SIZ]; 3694 | bool ena_sel = sel.bx != -1; 3695 | 3696 | if(sel.alt ^ IS_SET(focused_term, MODE_ALTSCREEN)) 3697 | ena_sel = 0; 3698 | 3699 | if(!(xw.state & WIN_VISIBLE)) 3700 | return; 3701 | 3702 | for(y = y1; y < y2; y++) { 3703 | if(!focused_term->dirty[y]) 3704 | continue; 3705 | 3706 | xtermclear(0, y, focused_term->col, y); 3707 | focused_term->dirty[y] = 0; 3708 | base = focused_term->line[y][0]; 3709 | ic = ib = ox = 0; 3710 | for(x = x1; x < x2; x++) { 3711 | new = focused_term->line[y][x]; 3712 | if(ena_sel && selected(x, y)) 3713 | new.mode ^= ATTR_REVERSE; 3714 | if(ib > 0 && (ATTRCMP(base, new) 3715 | || ib >= DRAW_BUF_SIZ-UTF_SIZ)) { 3716 | xdraws(buf, base, ox, y, ic, ib); 3717 | ic = ib = 0; 3718 | } 3719 | if(ib == 0) { 3720 | ox = x; 3721 | base = new; 3722 | } 3723 | 3724 | sl = utf8size(new.c); 3725 | memcpy(buf+ib, new.c, sl); 3726 | ib += sl; 3727 | ++ic; 3728 | } 3729 | if(ib > 0) 3730 | xdraws(buf, base, ox, y, ic, ib); 3731 | } 3732 | 3733 | xdrawcursor(); 3734 | xdrawbar(); 3735 | } 3736 | 3737 | void 3738 | xdrawbar(void) { 3739 | if (autohide) { 3740 | if (!terms->next) return; 3741 | } 3742 | 3743 | int i = 0; 3744 | int drawn = 1; 3745 | Term *term; 3746 | Glyph attr = {{' '}, ATTR_NULL, defaultbarfg, defaultbarbg}; 3747 | char buf[60]; 3748 | int buflen; 3749 | 3750 | if (shownewbutton && clicked_bar >= drawn && clicked_bar <= drawn + 3) { 3751 | clicked_bar = -1; 3752 | term_add(); 3753 | return; 3754 | } 3755 | 3756 | if (defaultbarbg != defaultbg) { 3757 | XftDrawRect(xw.draw, &dc.col[defaultbarbg], borderpx, 3758 | borderpx + focused_term->row * xw.ch, 3759 | (focused_term->col + 1) * xw.cw, xw.ch); 3760 | // To avoid a direct X call: 3761 | // char *bg = (char *)malloc(focused_term->col * sizeof(char)); 3762 | // memset(bg, ' ', focused_term->col * sizeof(char)); 3763 | // xdraws(bg, attr, 0, focused_term->row, focused_term->col, focused_term->col); 3764 | // free(bg); 3765 | } else { 3766 | xtermclear(0, focused_term->row, focused_term->col, focused_term->row); 3767 | } 3768 | 3769 | if (shownewbutton) { 3770 | attr.mode = ATTR_BOLD; 3771 | attr.fg = 10; 3772 | attr.bg = defaultbarbg; 3773 | xdraws("new", attr, drawn, focused_term->row, 3, 3); 3774 | drawn += 2; 3775 | drawn += 3; 3776 | } 3777 | 3778 | for (term = terms; term; term = term->next) { 3779 | i++; 3780 | if (term->title) { 3781 | if (alwaysshownumber) { 3782 | buflen = snprintf(buf, sizeof(buf), "%d:%s%s", i, term->title, 3783 | term->has_activity || term == focused_term ? "*" : " "); 3784 | } else { 3785 | buflen = snprintf(buf, sizeof(buf), "%s%s", term->title, 3786 | term->has_activity || term == focused_term ? "*" : " "); 3787 | } 3788 | } else { 3789 | buflen = snprintf(buf, sizeof(buf), "%d%s", i, 3790 | term->has_activity || term == focused_term ? "*" : " "); 3791 | } 3792 | if (term == focused_term) { 3793 | attr.mode = ATTR_NULL; 3794 | attr.fg = selbarfg; 3795 | attr.bg = selbarbg; 3796 | } else { 3797 | attr.mode = ATTR_NULL; 3798 | attr.fg = unselbarfg; 3799 | attr.bg = unselbarbg; 3800 | } 3801 | if (drawn + buflen > term->col) { 3802 | break; 3803 | } 3804 | if (clicked_bar >= drawn && clicked_bar <= drawn + buflen) { 3805 | /* we can return here, xdrawbar will just get called again anyway */ 3806 | clicked_bar = -1; 3807 | term_focus(term); 3808 | // Potentially do things like: 3809 | // if (clicked_mod) tstate = S_RENAME; 3810 | // if (clicked_mod) term_remove(term); 3811 | return; 3812 | } 3813 | xdraws(buf, attr, drawn, focused_term->row, buflen, buflen); 3814 | //drawn += 2; /* don't assume the state char is always present. */ 3815 | drawn += 1; /* always assume there's an extra space from the state char. */ 3816 | drawn += buflen; 3817 | } 3818 | 3819 | if (tstate == S_SEARCH || tstate == S_RENAME) { 3820 | attr.mode = ATTR_NULL; 3821 | attr.fg = defaultbarfg; 3822 | attr.bg = defaultbarbg; 3823 | drawn += 1; 3824 | if (drawn + 8 + entry.pos > focused_term->col) { 3825 | return; 3826 | } 3827 | char final[68]; 3828 | snprintf(final, sizeof(final), 3829 | tstate == S_SEARCH ? "Search: %s" : "Rename: %s", entry.text); 3830 | xdraws(final, attr, drawn, focused_term->row, 8 + entry.pos, 8 + entry.pos); 3831 | drawn += 8 + entry.pos; 3832 | attr.bg = defaultbarfg; 3833 | xdraws(" ", attr, drawn, focused_term->row, 1, 1); 3834 | drawn += 1; 3835 | } 3836 | 3837 | if (status_msg) { 3838 | int l = strlen(status_msg); 3839 | attr.mode = ATTR_NULL; 3840 | attr.fg = 1; 3841 | attr.bg = defaultbarbg; 3842 | drawn += 1; 3843 | if (drawn + l > focused_term->col) { 3844 | return; 3845 | } 3846 | xdraws(status_msg, attr, drawn, focused_term->row, l, l); 3847 | } 3848 | } 3849 | 3850 | void 3851 | expose(XEvent *ev) { 3852 | XExposeEvent *e = &ev->xexpose; 3853 | 3854 | if(xw.state & WIN_REDRAW) { 3855 | if(!e->count) 3856 | xw.state &= ~WIN_REDRAW; 3857 | } 3858 | redraw(0); 3859 | } 3860 | 3861 | void 3862 | visibility(XEvent *ev) { 3863 | XVisibilityEvent *e = &ev->xvisibility; 3864 | 3865 | if(e->state == VisibilityFullyObscured) { 3866 | xw.state &= ~WIN_VISIBLE; 3867 | } else if(!(xw.state & WIN_VISIBLE)) { 3868 | /* need a full redraw for next Expose, not just a buf copy */ 3869 | xw.state |= WIN_VISIBLE | WIN_REDRAW; 3870 | } 3871 | } 3872 | 3873 | void 3874 | unmap(XEvent *ev) { 3875 | xw.state &= ~WIN_VISIBLE; 3876 | } 3877 | 3878 | void 3879 | xseturgency(int add) { 3880 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 3881 | 3882 | h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint); 3883 | XSetWMHints(xw.dpy, xw.win, h); 3884 | XFree(h); 3885 | } 3886 | 3887 | void 3888 | focus(XEvent *ev) { 3889 | XFocusChangeEvent *e = &ev->xfocus; 3890 | 3891 | if(e->mode == NotifyGrab) 3892 | return; 3893 | 3894 | if(ev->type == FocusIn) { 3895 | XSetICFocus(xw.xic); 3896 | xw.state |= WIN_FOCUSED; 3897 | xseturgency(0); 3898 | } else { 3899 | XUnsetICFocus(xw.xic); 3900 | xw.state &= ~WIN_FOCUSED; 3901 | } 3902 | } 3903 | 3904 | inline bool 3905 | match(uint mask, uint state) { 3906 | state &= ~(ignoremod); 3907 | 3908 | if(mask == XK_NO_MOD && state) 3909 | return false; 3910 | if(mask != XK_ANY_MOD && mask != XK_NO_MOD && !state) 3911 | return false; 3912 | if((state & mask) != state) 3913 | return false; 3914 | return true; 3915 | } 3916 | 3917 | void 3918 | numlock(const Arg *dummy) { 3919 | focused_term->numlock ^= 1; 3920 | } 3921 | 3922 | char* 3923 | kmap(KeySym k, uint state) { 3924 | uint mask; 3925 | Key *kp; 3926 | int i; 3927 | 3928 | /* Check for mapped keys out of X11 function keys. */ 3929 | for(i = 0; i < LEN(mappedkeys); i++) { 3930 | if(mappedkeys[i] == k) 3931 | break; 3932 | } 3933 | if(i == LEN(mappedkeys)) { 3934 | if((k & 0xFFFF) < 0xFD00) 3935 | return NULL; 3936 | } 3937 | 3938 | for(kp = key; kp < key + LEN(key); kp++) { 3939 | mask = kp->mask; 3940 | 3941 | if(kp->k != k) 3942 | continue; 3943 | 3944 | if(!match(mask, state)) 3945 | continue; 3946 | 3947 | if(kp->appkey > 0) { 3948 | if(!IS_SET(focused_term, MODE_APPKEYPAD)) 3949 | continue; 3950 | if(focused_term->numlock && kp->appkey == 2) 3951 | continue; 3952 | } else if(kp->appkey < 0 && IS_SET(focused_term, MODE_APPKEYPAD)) { 3953 | continue; 3954 | } 3955 | 3956 | if((kp->appcursor < 0 && IS_SET(focused_term, MODE_APPCURSOR)) || 3957 | (kp->appcursor > 0 3958 | && !IS_SET(focused_term, MODE_APPCURSOR))) { 3959 | continue; 3960 | } 3961 | 3962 | if((kp->crlf < 0 && IS_SET(focused_term, MODE_CRLF)) || 3963 | (kp->crlf > 0 && !IS_SET(focused_term, MODE_CRLF))) { 3964 | continue; 3965 | } 3966 | 3967 | return kp->s; 3968 | } 3969 | 3970 | return NULL; 3971 | } 3972 | 3973 | void 3974 | kpress(XEvent *ev) { 3975 | XKeyEvent *e = &ev->xkey; 3976 | KeySym ksym; 3977 | char xstr[31], buf[32], *customkey, *cp = buf; 3978 | int len, ret; 3979 | long c; 3980 | Status status; 3981 | Shortcut *bp; 3982 | 3983 | #ifdef USE_BLANK_CURSOR 3984 | xcursorblank(); 3985 | #endif 3986 | 3987 | if(IS_SET(focused_term, MODE_KBDLOCK)) 3988 | return; 3989 | 3990 | len = XmbLookupString(xw.xic, e, xstr, sizeof(xstr), &ksym, &status); 3991 | e->state &= ~Mod2Mask; 3992 | 3993 | #define SEND_MOUSE(f) do { \ 3994 | XEvent ev; \ 3995 | ev.xbutton.button = Button1; \ 3996 | ev.xbutton.state |= Button1Mask; \ 3997 | ev.xbutton.x = (term->c.x * xw.cw) + borderpx; \ 3998 | ev.xbutton.y = (term->c.y * xw.ch) + borderpx; \ 3999 | (f)(&ev); \ 4000 | } while (0) 4001 | 4002 | #define UPDATE_SCROLL \ 4003 | set_message("%d%%", \ 4004 | (-term->ybase * 100) \ 4005 | / (term->sb_total + term->row)), xdrawbar() 4006 | 4007 | #define GET_LINE(t, i) \ 4008 | ((i) >= 0 \ 4009 | ? ((t)->ybase == 0 ? (t)->line[(i)] : (t)->last_line[(i)]) \ 4010 | : scrollback_get(term, -((i) + 1))) 4011 | 4012 | /* 0. prefix - C-a */ 4013 | if (tstate == S_RENAME) { 4014 | Term *term = focused_term; 4015 | if (ksym == XK_Escape) { 4016 | tstate = S_NORMAL; 4017 | xdrawbar(); 4018 | return; 4019 | } 4020 | if (ksym == XK_Return) { 4021 | tstate = S_NORMAL; 4022 | if (entry.pos == 0) { 4023 | xdrawbar(); 4024 | return; 4025 | } 4026 | entry.text[entry.pos] = '\0'; 4027 | term->title = strdup(entry.text); 4028 | xdrawbar(); 4029 | return; 4030 | } 4031 | if (ksym == XK_BackSpace) { 4032 | if (entry.pos == 0) return; 4033 | entry.text[--entry.pos] = '\0'; 4034 | xdrawbar(); 4035 | return; 4036 | } 4037 | if (len == 1) { 4038 | if (entry.pos < sizeof(entry.text) - 1) { 4039 | entry.text[entry.pos++] = xstr[0]; 4040 | entry.text[entry.pos] = '\0'; 4041 | xdrawbar(); 4042 | } 4043 | return; 4044 | } 4045 | return; 4046 | } 4047 | 4048 | if (tstate == S_SEARCH || (tstate >= S_SELECT && (ksym == XK_n || ksym == XK_N))) { 4049 | Term *term = focused_term; 4050 | if (ksym == XK_Escape) { 4051 | tstate = S_SELECT; 4052 | redraw(0); 4053 | return; 4054 | } 4055 | if (ksym == XK_Return || tstate != S_SEARCH) { 4056 | tstate = S_SELECT; 4057 | 4058 | if (entry.pos == 0) { 4059 | redraw(0); 4060 | return; 4061 | } 4062 | 4063 | entry.text[entry.pos] = '\0'; 4064 | 4065 | Glyph *line; 4066 | bool found = false; 4067 | bool wrapped = false; 4068 | int x = term->c.x + 1; 4069 | int y = term->c.y + term->ybase; 4070 | int yb, i; 4071 | bool up = ksym == XK_N 4072 | ? !entry.flag 4073 | : entry.flag; 4074 | 4075 | for (;;) { 4076 | line = GET_LINE(term, y); 4077 | 4078 | while (x < term->col) { 4079 | for (i = 0; i < entry.pos; i++) { 4080 | if (x + i >= term->col) break; 4081 | if (line[x + i].c[0] != entry.text[i]) { 4082 | break; 4083 | } else if (line[x + i].c[0] == entry.text[i] && i == entry.pos - 1) { 4084 | found = true; 4085 | break; 4086 | } 4087 | } 4088 | if (found) break; 4089 | x += i + 1; 4090 | } 4091 | if (found) break; 4092 | 4093 | x = 0; 4094 | 4095 | if (!up) { 4096 | y++; 4097 | if (y >= term->row) { 4098 | if (wrapped) break; 4099 | set_message("Search wrapped. Continuing at TOP."); 4100 | wrapped = true; 4101 | y = -term->sb_total; 4102 | } 4103 | } else { 4104 | y--; 4105 | if (y < 0) { 4106 | if (wrapped) break; 4107 | set_message("Search wrapped. Continuing at BOTTOM."); 4108 | wrapped = true; 4109 | y = term->row - 1; 4110 | } 4111 | } 4112 | } 4113 | 4114 | if (found) { 4115 | if (y < 0) { 4116 | yb = y; 4117 | y = 0; 4118 | } else { 4119 | yb = 0; 4120 | } 4121 | tscrollback(term, -term->ybase + yb); 4122 | tmoveto(term, x, y); 4123 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4124 | if (!wrapped) UPDATE_SCROLL; 4125 | } else { 4126 | set_message("No matches found."); 4127 | } 4128 | 4129 | redraw(0); 4130 | return; 4131 | } 4132 | if (ksym == XK_BackSpace) { 4133 | if (entry.pos == 0) return; 4134 | entry.text[--entry.pos] = '\0'; 4135 | xdrawbar(); 4136 | return; 4137 | } 4138 | if (len == 1) { 4139 | if (entry.pos < sizeof(entry.text) - 1) { 4140 | entry.text[entry.pos++] = xstr[0]; 4141 | entry.text[entry.pos] = '\0'; 4142 | xdrawbar(); 4143 | } 4144 | return; 4145 | } 4146 | return; 4147 | } 4148 | 4149 | if (tstate >= S_SELECT) { 4150 | Term *term = focused_term; 4151 | if (ksym == XK_q || ksym == XK_Escape) { 4152 | if (tstate == S_VISUAL) { 4153 | SEND_MOUSE(brelease); 4154 | SEND_MOUSE(bpress); 4155 | SEND_MOUSE(brelease); 4156 | } 4157 | tstate = S_NORMAL; 4158 | set_message(NULL); 4159 | tscrollback(term, normal_cursor.ybase - term->ybase); 4160 | tmoveto(term, normal_cursor.x, normal_cursor.y); 4161 | if (normal_cursor.hidden) term->mode |= MODE_HIDE; 4162 | redraw(0); 4163 | } else if (ksym == XK_slash || ksym == XK_question) { 4164 | tstate = S_SEARCH; 4165 | entry.flag = ksym == XK_question; 4166 | entry.text[0] = '\0'; 4167 | entry.pos = 0; 4168 | xdrawbar(); 4169 | } else if (ksym == XK_h) { 4170 | tmoveto(term, term->c.x - 1, term->c.y); 4171 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4172 | } else if (ksym == XK_j) { 4173 | if (term->c.y == term->row - 1) { 4174 | tscrollback(term, 1); 4175 | } else { 4176 | tmoveto(term, term->c.x, term->c.y + 1); 4177 | } 4178 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4179 | UPDATE_SCROLL; 4180 | } else if (ksym == XK_k) { 4181 | if (term->c.y == 0) { 4182 | tscrollback(term, -1); 4183 | } else { 4184 | tmoveto(term, term->c.x, term->c.y - 1); 4185 | } 4186 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4187 | UPDATE_SCROLL; 4188 | } else if (ksym == XK_l) { 4189 | tmoveto(term, term->c.x + 1, term->c.y); 4190 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4191 | } else if (ksym == XK_H || ksym == XK_M || ksym == XK_L) { 4192 | if (ksym == XK_H) { 4193 | tmoveto(term, 0, 0); 4194 | } else if (ksym == XK_M) { 4195 | tmoveto(term, 0, term->row / 2); 4196 | } else if (ksym == XK_L) { 4197 | tmoveto(term, 0, term->row - 1); 4198 | } 4199 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4200 | UPDATE_SCROLL; 4201 | } else if (ksym == XK_g || ksym == XK_G) { 4202 | if (ksym == XK_g) { 4203 | tscrollback(term, -term->ybase + -term->sb_total); 4204 | tmoveto(term, 0, 0); 4205 | } else if (ksym == XK_G) { 4206 | tscrollback(term, -term->ybase + 0); 4207 | tmoveto(term, 0, term->row - 1); 4208 | } 4209 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4210 | UPDATE_SCROLL; 4211 | } else if (ksym == XK_0 || ksym == XK_asciicircum) { 4212 | if (ksym == XK_0) { 4213 | tmoveto(term, 0, term->c.y); 4214 | } else if (ksym == XK_asciicircum) { 4215 | Glyph *l = term->line[term->c.y]; 4216 | int x = 0; 4217 | while (x < term->col) { 4218 | if (l[x].c[0] > ' ') { 4219 | break; 4220 | } 4221 | x++; 4222 | } 4223 | if (x >= term->col) x = term->col - 1; 4224 | tmoveto(term, x, term->c.y); 4225 | } 4226 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4227 | } else if (ksym == XK_w || ksym == XK_W) { 4228 | int x = term->c.x; 4229 | int y = term->c.y; 4230 | int yb = term->ybase; 4231 | bool saw_space = false; 4232 | 4233 | for (;;) { 4234 | Glyph *l = GET_LINE(term, y + yb); 4235 | while (x < term->col) { 4236 | if (l[x].c[0] <= ' ') { 4237 | saw_space = true; 4238 | } else if (saw_space) { 4239 | break; 4240 | } 4241 | x++; 4242 | } 4243 | if (x >= term->col) x = term->col - 1; 4244 | if (x == term->col - 1 && l[x].c[0] <= ' ') { 4245 | x = 0; 4246 | if (++y >= term->row) { 4247 | y--; 4248 | if (++yb > 0) { 4249 | yb = 0; 4250 | x = term->c.x; 4251 | break; 4252 | } 4253 | } 4254 | continue; 4255 | } 4256 | break; 4257 | } 4258 | 4259 | tscrollback(term, -term->ybase + yb); 4260 | tmoveto(term, x, y); 4261 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4262 | } else if (ksym == XK_e || ksym == XK_E) { 4263 | int x = term->c.x + 1; 4264 | int y = term->c.y; 4265 | int yb = term->ybase; 4266 | if (x >= term->col) x--; 4267 | 4268 | for (;;) { 4269 | Glyph *l = GET_LINE(term, y + yb); 4270 | while (x < term->col) { 4271 | if (l[x].c[0] <= ' ') { 4272 | x++; 4273 | } else { 4274 | break; 4275 | } 4276 | } 4277 | while (x < term->col) { 4278 | if (l[x].c[0] <= ' ') { 4279 | if (x - 1 >= 0 && l[x-1].c[0] > ' ') { 4280 | x--; 4281 | break; 4282 | } 4283 | } 4284 | x++; 4285 | } 4286 | if (x >= term->col) x = term->col - 1; 4287 | if (x == term->col - 1 && l[x].c[0] <= ' ') { 4288 | x = 0; 4289 | if (++y >= term->row) { 4290 | y--; 4291 | if (++yb > 0) { 4292 | yb = 0; 4293 | break; 4294 | } 4295 | } 4296 | continue; 4297 | } 4298 | break; 4299 | } 4300 | 4301 | tscrollback(term, -term->ybase + yb); 4302 | tmoveto(term, x, y); 4303 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4304 | } else if ((ksym == XK_b || ksym == XK_B) && !match(ControlMask, e->state)) { 4305 | int x = term->c.x; 4306 | int y = term->c.y; 4307 | int yb = term->ybase; 4308 | 4309 | for (;;) { 4310 | Glyph *l = GET_LINE(term, y + yb); 4311 | bool saw_space = x > 0 && l[x].c[0] > ' ' && l[x-1].c[0] > ' '; 4312 | while (x >= 0) { 4313 | if (l[x].c[0] <= ' ') { 4314 | if (saw_space && (x + 1 < term->col && l[x+1].c[0] > ' ')) { 4315 | x++; 4316 | break; 4317 | } else { 4318 | saw_space = true; 4319 | } 4320 | } 4321 | x--; 4322 | } 4323 | if (x < 0) x = 0; 4324 | if (x == 0 && (l[x].c[0] <= ' ' || !saw_space)) { 4325 | x = term->col - 1; 4326 | if (--y < 0) { 4327 | y++; 4328 | if (--yb < -term->sb_total) { 4329 | yb++; 4330 | x = 0; 4331 | break; 4332 | } 4333 | } 4334 | continue; 4335 | } 4336 | break; 4337 | } 4338 | 4339 | tscrollback(term, -term->ybase + yb); 4340 | tmoveto(term, x, y); 4341 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4342 | } else if (ksym == XK_dollar) { 4343 | Glyph *l = term->line[term->c.y]; 4344 | int x = term->col - 1; 4345 | while (x >= 0) { 4346 | if (l[x].c[0] > ' ') { 4347 | //if (tstate == S_VISUAL && x < term->col - 1) x++; 4348 | break; 4349 | } 4350 | x--; 4351 | } 4352 | if (x < 0) x = 0; 4353 | tmoveto(term, x, term->c.y); 4354 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4355 | } else if (ksym == XK_braceleft || ksym == XK_braceright) { 4356 | Glyph *line; 4357 | bool saw_full = false; 4358 | bool found = false; 4359 | int first_is_space = -1; 4360 | int y = term->c.y + (ksym == XK_braceleft ? -1 : 1); 4361 | int yb = term->ybase; 4362 | int i; 4363 | 4364 | if (ksym == XK_braceleft) { 4365 | if (y < 0) { 4366 | y++; 4367 | if (yb > -term->sb_total) yb--; 4368 | } 4369 | } else if (ksym == XK_braceright) { 4370 | if (y >= term->row) { 4371 | y--; 4372 | if (yb < 0) yb++; 4373 | } 4374 | } 4375 | 4376 | for (;;) { 4377 | line = GET_LINE(term, y + yb); 4378 | 4379 | for (i = 0; i < term->col; i++) { 4380 | if (line[i].c[0] > ' ') { 4381 | if (first_is_space == -1) { 4382 | first_is_space = 0; 4383 | } 4384 | saw_full = true; 4385 | break; 4386 | } else if (i == term->col - 1) { 4387 | if (first_is_space == -1) { 4388 | first_is_space = 1; 4389 | } else if (first_is_space == 0) { 4390 | found = true; 4391 | } else if (first_is_space == 1) { 4392 | if (saw_full) found = true; 4393 | } 4394 | break; 4395 | } 4396 | } 4397 | 4398 | if (found) break; 4399 | 4400 | if (ksym == XK_braceleft) { 4401 | y--; 4402 | if (y < 0) { 4403 | y++; 4404 | if (yb > -term->sb_total) yb--; 4405 | else break; 4406 | } 4407 | } else if (ksym == XK_braceright) { 4408 | y++; 4409 | if (y >= term->row) { 4410 | y--; 4411 | if (yb < 0) yb++; 4412 | else break; 4413 | } 4414 | } 4415 | } 4416 | 4417 | if (!found) { 4418 | if (ksym == XK_braceleft) { 4419 | y = 0; 4420 | yb = -term->sb_total; 4421 | } else if (ksym == XK_braceright) { 4422 | y = term->row - 1; 4423 | yb = 0; 4424 | } 4425 | } 4426 | 4427 | tscrollback(term, -term->ybase + yb); 4428 | tmoveto(term, 0, y); 4429 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4430 | UPDATE_SCROLL; 4431 | } else if (ksym == XK_u && match(ControlMask, e->state)) { 4432 | tscrollback(term, -(term->row / 2)); 4433 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4434 | UPDATE_SCROLL; 4435 | } else if (ksym == XK_d && match(ControlMask, e->state)) { 4436 | tscrollback(term, term->row / 2); 4437 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4438 | UPDATE_SCROLL; 4439 | } else if (ksym == XK_b && match(ControlMask, e->state)) { 4440 | tscrollback(term, -term->row); 4441 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4442 | UPDATE_SCROLL; 4443 | } else if (ksym == XK_f && match(ControlMask, e->state)) { 4444 | tscrollback(term, term->row); 4445 | if (tstate == S_VISUAL) SEND_MOUSE(bmotion); 4446 | UPDATE_SCROLL; 4447 | } else if (ksym == XK_v || ksym == XK_V) { 4448 | if (tstate == S_SELECT) { 4449 | tstate = S_VISUAL; 4450 | SEND_MOUSE(bpress); 4451 | } else if (tstate == S_VISUAL) { 4452 | SEND_MOUSE(brelease); 4453 | SEND_MOUSE(bpress); 4454 | SEND_MOUSE(brelease); 4455 | tstate = S_SELECT; 4456 | } 4457 | } else if (ksym == XK_y) { 4458 | if (tstate == S_VISUAL) { 4459 | SEND_MOUSE(brelease); 4460 | SEND_MOUSE(bpress); 4461 | SEND_MOUSE(brelease); 4462 | tstate = S_NORMAL; 4463 | set_message(NULL); 4464 | tscrollback(term, normal_cursor.ybase - term->ybase); 4465 | tmoveto(term, normal_cursor.x, normal_cursor.y); 4466 | if (normal_cursor.hidden) term->mode |= MODE_HIDE; 4467 | redraw(0); 4468 | } 4469 | } 4470 | return; 4471 | } 4472 | 4473 | if (ksym == XK_a && match(ControlMask, e->state)) { 4474 | if (tstate == S_NORMAL) { 4475 | tstate = S_PREFIX; 4476 | return; 4477 | } 4478 | } 4479 | 4480 | if (tstate == S_PREFIX) { 4481 | Term *term = focused_term; 4482 | if (ksym == XK_bracketleft) { 4483 | tstate = S_SELECT; 4484 | normal_cursor.x = term->c.x; 4485 | normal_cursor.y = term->c.y; 4486 | normal_cursor.hidden = term->mode & MODE_HIDE; 4487 | normal_cursor.ybase = term->ybase; 4488 | term->mode &= ~MODE_HIDE; 4489 | tmoveto(term, 0, term->row - 1); 4490 | UPDATE_SCROLL; 4491 | return; 4492 | } 4493 | if (ksym == XK_r) { 4494 | tstate = S_RENAME; 4495 | entry.text[0] = '\0'; 4496 | entry.pos = 0; 4497 | xdrawbar(); 4498 | return; 4499 | } 4500 | if (ksym == XK_p || ksym == XK_bracketright) { 4501 | selpaste(NULL); 4502 | } else if (ksym == XK_c) { 4503 | term_add(); 4504 | } else if (ksym == XK_k || ksym == XK_ampersand) { 4505 | term_remove(term); 4506 | } else if (ksym >= XK_1 && ksym <= XK_9) { 4507 | term_focus_idx(ksym - XK_0); 4508 | } else if (ksym == XK_N) { 4509 | term_focus_prev(term); 4510 | } else if (ksym == XK_n) { 4511 | term_focus_next(term); 4512 | } else if (ksym == XK_Shift_L || ksym == XK_Shift_R) { 4513 | return; 4514 | } 4515 | tstate = S_NORMAL; 4516 | return; 4517 | } 4518 | #undef SEND_MOUSE 4519 | #undef UPDATE_SCROLL 4520 | #undef GET_LINE 4521 | 4522 | /* 1. shortcuts */ 4523 | for(bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 4524 | if(ksym == bp->keysym && match(bp->mod, e->state)) { 4525 | bp->func(&(bp->arg)); 4526 | return; 4527 | } 4528 | } 4529 | 4530 | /* 2. custom keys from config.h */ 4531 | if((customkey = kmap(ksym, e->state))) { 4532 | len = strlen(customkey); 4533 | memcpy(buf, customkey, len); 4534 | /* 3. hardcoded (overrides X lookup) */ 4535 | } else { 4536 | if(len == 0) 4537 | return; 4538 | 4539 | if(len == 1 && e->state & Mod1Mask) { 4540 | if(IS_SET(focused_term, MODE_8BIT)) { 4541 | if(*xstr < 0177) { 4542 | c = *xstr | B7; 4543 | ret = utf8encode(&c, cp); 4544 | cp += ret; 4545 | len = 0; 4546 | } 4547 | } else { 4548 | *cp++ = '\033'; 4549 | } 4550 | } 4551 | 4552 | memcpy(cp, xstr, len); 4553 | len = cp - buf + len; 4554 | } 4555 | 4556 | ttywrite(focused_term, buf, len); 4557 | if(IS_SET(focused_term, MODE_ECHO)) 4558 | techo(focused_term, buf, len); 4559 | } 4560 | 4561 | 4562 | void 4563 | cmessage(XEvent *e) { 4564 | /* 4565 | * See xembed specs 4566 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 4567 | */ 4568 | if(e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 4569 | if(e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 4570 | xw.state |= WIN_FOCUSED; 4571 | xseturgency(0); 4572 | } else if(e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 4573 | xw.state &= ~WIN_FOCUSED; 4574 | } 4575 | } else if(e->xclient.data.l[0] == xw.wmdeletewin) { 4576 | /* Send SIGHUP to shell */ 4577 | Term *term; 4578 | for (term = terms; term; term = term->next) { 4579 | kill(term->pid, SIGHUP); 4580 | } 4581 | exit(EXIT_SUCCESS); 4582 | } 4583 | } 4584 | 4585 | void 4586 | cresize(int width, int height) { 4587 | int col, row; 4588 | 4589 | if(width != 0) 4590 | xw.w = width; 4591 | if(height != 0) 4592 | xw.h = height; 4593 | 4594 | col = (xw.w - 2 * borderpx) / xw.cw; 4595 | row = (xw.h - 2 * borderpx) / xw.ch; 4596 | 4597 | if (!autohide || terms->next) { 4598 | if (--row < 0) row = 0; 4599 | } 4600 | 4601 | Term *term; 4602 | for (term = terms; term; term = term->next) { 4603 | tresize(term, col, row); 4604 | } 4605 | xresize(col, row); 4606 | for (term = terms; term; term = term->next) { 4607 | ttyresize(term); 4608 | } 4609 | } 4610 | 4611 | void 4612 | resize(XEvent *e) { 4613 | if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) 4614 | return; 4615 | 4616 | cresize(e->xconfigure.width, e->xconfigure.height); 4617 | } 4618 | 4619 | void 4620 | term_add(void) { 4621 | Term *term; 4622 | 4623 | if (!terms) { 4624 | terms = (Term *)xmalloc(sizeof(Term)); 4625 | memset(terms, 0, sizeof(Term)); 4626 | focused_term = terms; 4627 | tnew(focused_term, 80, 24); 4628 | ttynew(focused_term); 4629 | } else { 4630 | for (term = terms; term; term = term->next) { 4631 | if (!term->next) break; 4632 | } 4633 | if (!term) die("no terminal found\n"); 4634 | term->next = (Term *)xmalloc(sizeof(Term)); 4635 | memset(term->next, 0, sizeof(Term)); 4636 | focused_term = term->next; 4637 | tnew(focused_term, terms->col, terms->row); 4638 | char **temp = opt_cmd; 4639 | opt_cmd = NULL; 4640 | ttynew(focused_term); 4641 | opt_cmd = temp; 4642 | } 4643 | 4644 | if (autohide) { 4645 | // Another tab was created. 4646 | if (terms->next && !terms->next->next) { 4647 | cresize(0, 0); 4648 | } 4649 | } 4650 | 4651 | redraw(0); 4652 | } 4653 | 4654 | void 4655 | term_remove(Term *target) { 4656 | int i; 4657 | 4658 | if (terms == target) { 4659 | terms = terms->next; 4660 | if (!terms) exit(EXIT_SUCCESS); 4661 | focused_term = terms; 4662 | } else { 4663 | Term *term; 4664 | for (term = terms; term; term = term->next) { 4665 | if (term->next == target) break; 4666 | } 4667 | if (!term) die("no terminal found\n"); 4668 | term->next = target->next; 4669 | focused_term = term; 4670 | } 4671 | 4672 | // Free up memory 4673 | for (i = 0; i < target->row; i++) { 4674 | free(target->line[i]); 4675 | free(target->alt[i]); 4676 | free(target->last_line[i]); 4677 | } 4678 | 4679 | for (i = 0; i < scrollback; i++) { 4680 | free(target->sb[i]); 4681 | } 4682 | 4683 | free(target->line); 4684 | free(target->alt); 4685 | free(target->last_line); 4686 | free(target->sb); 4687 | free(target->dirty); 4688 | free(target->tabs); 4689 | free(target); 4690 | 4691 | if (autohide) { 4692 | // Fell back to one tab. 4693 | if (!terms->next) { 4694 | cresize(0, 0); 4695 | } 4696 | } 4697 | 4698 | redraw(0); 4699 | } 4700 | 4701 | void 4702 | term_focus(Term *target) { 4703 | focused_term->has_activity = false; 4704 | focused_term = target == NULL ? terms : target; 4705 | focused_term->has_activity = false; 4706 | redraw(0); 4707 | } 4708 | 4709 | void 4710 | term_focus_prev(Term *target) { 4711 | Term *term; 4712 | for (term = terms; term; term = term->next) { 4713 | if (target == terms && !term->next) { 4714 | break; 4715 | } 4716 | if (term->next == target) { 4717 | break; 4718 | } 4719 | } 4720 | term_focus(term); 4721 | } 4722 | 4723 | void 4724 | term_focus_next(Term *target) { 4725 | term_focus(target ? target->next : NULL); 4726 | } 4727 | 4728 | void 4729 | term_focus_idx(int idx) { 4730 | int i = 0; 4731 | Term *term; 4732 | for (term = terms; term; term = term->next) { 4733 | if (++i == idx) { 4734 | term_focus(term); 4735 | break; 4736 | } 4737 | } 4738 | } 4739 | 4740 | void 4741 | run(void) { 4742 | XEvent ev; 4743 | fd_set rfd; 4744 | int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; 4745 | struct timeval drawtimeout, *tv = NULL, now, last, lastblink; 4746 | 4747 | gettimeofday(&lastblink, NULL); 4748 | gettimeofday(&last, NULL); 4749 | 4750 | for(xev = actionfps;;) { 4751 | Term *term, *next; 4752 | int lastfd = 0; 4753 | 4754 | FD_ZERO(&rfd); 4755 | for (term = terms; term; term = term->next) { 4756 | FD_SET(term->cmdfd, &rfd); 4757 | lastfd = MAX(term->cmdfd, lastfd); 4758 | } 4759 | FD_SET(xfd, &rfd); 4760 | 4761 | if(select(MAX(xfd, lastfd)+1, &rfd, NULL, NULL, tv) < 0) { 4762 | if(errno == EINTR) 4763 | continue; 4764 | die("select failed: %s\n", SERRNO); 4765 | } 4766 | 4767 | for (term = terms; term; term = next) { 4768 | next = term->next; 4769 | if(FD_ISSET(term->cmdfd, &rfd)) { 4770 | if (ttyread(term) == -1) { 4771 | /* potentially move if-block from ttyread here */ 4772 | continue; 4773 | } 4774 | /* potentially only do this and the below blink code for focused_term */ 4775 | if(blinktimeout) { 4776 | blinkset = tattrset(term, ATTR_BLINK); 4777 | if(!blinkset && term->mode & ATTR_BLINK) 4778 | term->mode &= ~(MODE_BLINK); 4779 | } 4780 | } 4781 | } 4782 | 4783 | if(FD_ISSET(xfd, &rfd)) 4784 | xev = actionfps; 4785 | 4786 | gettimeofday(&now, NULL); 4787 | drawtimeout.tv_sec = 0; 4788 | drawtimeout.tv_usec = (1000/xfps) * 1000; 4789 | tv = &drawtimeout; 4790 | 4791 | #ifndef NO_PROC_POLL 4792 | if (TIMEDIFF(now, last_getproc) > 2000) { 4793 | for (term = terms; term; term = term->next) { 4794 | char *title = getproc(term->cmdfd, NULL); 4795 | if (title == NULL) continue; 4796 | if (term->title) { 4797 | free(term->title); 4798 | term->title = NULL; 4799 | } 4800 | char *btitle = basename(title); 4801 | if (btitle == NULL) { 4802 | free(title); 4803 | continue; 4804 | } 4805 | char *atitle = strdup(btitle); 4806 | free(title); 4807 | term->title = atitle; 4808 | } 4809 | gettimeofday(&last_getproc, NULL); 4810 | } 4811 | #endif 4812 | 4813 | dodraw = 0; 4814 | if(blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { 4815 | for (term = terms; term; term = term->next) { 4816 | tsetdirtattr(term, ATTR_BLINK); 4817 | term->mode ^= MODE_BLINK; 4818 | } 4819 | gettimeofday(&lastblink, NULL); 4820 | dodraw = 1; 4821 | } 4822 | if(TIMEDIFF(now, last) \ 4823 | > (xev? (1000/xfps) : (1000/actionfps))) { 4824 | dodraw = 1; 4825 | last = now; 4826 | } 4827 | 4828 | if(dodraw) { 4829 | while(XPending(xw.dpy)) { 4830 | XNextEvent(xw.dpy, &ev); 4831 | if(XFilterEvent(&ev, None)) 4832 | continue; 4833 | if(handler[ev.type]) 4834 | (handler[ev.type])(&ev); 4835 | } 4836 | 4837 | if (status_msg && TIMEDIFF(now, status_time) > 3000) { 4838 | free(status_msg); 4839 | status_msg = NULL; 4840 | } 4841 | 4842 | draw(); 4843 | XFlush(xw.dpy); 4844 | 4845 | if(xev && !FD_ISSET(xfd, &rfd)) 4846 | xev--; 4847 | if(!FD_ISSET(focused_term->cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) { 4848 | if(blinkset) { 4849 | if(TIMEDIFF(now, lastblink) \ 4850 | > blinktimeout) { 4851 | drawtimeout.tv_usec = 1; 4852 | } else { 4853 | drawtimeout.tv_usec = (1000 * \ 4854 | (blinktimeout - \ 4855 | TIMEDIFF(now, 4856 | lastblink))); 4857 | } 4858 | } else { 4859 | tv = NULL; 4860 | } 4861 | } 4862 | } 4863 | } 4864 | } 4865 | 4866 | void 4867 | usage(void) { 4868 | die("%s " VERSION " (c) 2010-2013 st engineers\n" \ 4869 | "usage: st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]" \ 4870 | " [-t title] [-w windowid] [-e command ...]\n", argv0); 4871 | } 4872 | 4873 | int 4874 | main(int argc, char *argv_[]) { 4875 | int bitm, xr, yr; 4876 | uint wr, hr; 4877 | 4878 | xw.fw = xw.fh = xw.fx = xw.fy = 0; 4879 | xw.isfixed = False; 4880 | 4881 | char **argv = (char **)xmalloc((argc + 1) * sizeof(char *)); 4882 | argv[argc] = NULL; 4883 | 4884 | for (int i = 0; i < argc; i++) { 4885 | argv[i] = strdup(argv_[i]); 4886 | if (strcmp(argv[i], "-name") == 0) argv[i][2] = '\0'; 4887 | } 4888 | 4889 | ARGBEGIN { 4890 | case 'a': 4891 | allowaltscreen = false; 4892 | break; 4893 | case 'c': 4894 | opt_class = EARGF(usage()); 4895 | break; 4896 | case 'e': 4897 | /* eat all remaining arguments */ 4898 | if(argc > 1) 4899 | opt_cmd = &argv[1]; 4900 | goto run; 4901 | case 'f': 4902 | opt_font = EARGF(usage()); 4903 | break; 4904 | case 'g': 4905 | bitm = XParseGeometry(EARGF(usage()), &xr, &yr, &wr, &hr); 4906 | if(bitm & XValue) 4907 | xw.fx = xr; 4908 | if(bitm & YValue) 4909 | xw.fy = yr; 4910 | if(bitm & WidthValue) 4911 | xw.fw = (int)wr; 4912 | if(bitm & HeightValue) 4913 | xw.fh = (int)hr; 4914 | if(bitm & XNegative && xw.fx == 0) 4915 | xw.fx = -1; 4916 | if(bitm & XNegative && xw.fy == 0) 4917 | xw.fy = -1; 4918 | 4919 | if(xw.fh != 0 && xw.fw != 0) 4920 | xw.isfixed = True; 4921 | break; 4922 | case 'o': 4923 | opt_io = EARGF(usage()); 4924 | break; 4925 | case 't': 4926 | case 'T': 4927 | opt_title = EARGF(usage()); 4928 | break; 4929 | case 'n': 4930 | opt_name = EARGF(usage()); 4931 | break; 4932 | case 'w': 4933 | opt_embed = EARGF(usage()); 4934 | break; 4935 | case 'v': 4936 | default: 4937 | usage(); 4938 | } ARGEND; 4939 | 4940 | run: 4941 | setlocale(LC_CTYPE, ""); 4942 | XSetLocaleModifiers(""); 4943 | // term_add(); 4944 | terms = (Term *)xmalloc(sizeof(Term)); 4945 | memset(terms, 0, sizeof(Term)); 4946 | focused_term = terms; 4947 | tnew(focused_term, 80, 24); 4948 | xinit(); 4949 | #ifndef NO_TABS 4950 | signal(SIGCHLD, sigchld); 4951 | #endif 4952 | ttynew(focused_term); 4953 | selinit(); 4954 | if(xw.isfixed) 4955 | cresize(xw.h, xw.w); 4956 | run(); 4957 | 4958 | return 0; 4959 | } 4960 | 4961 | /** 4962 | * getproc 4963 | * Taken from tmux. 4964 | */ 4965 | 4966 | // Taken from: tmux (http://tmux.sourceforge.net/) 4967 | // Copyright (c) 2009 Nicholas Marriott 4968 | // Copyright (c) 2009 Joshua Elsasser 4969 | // Copyright (c) 2009 Todd Carson 4970 | // 4971 | // Permission to use, copy, modify, and distribute this software for any 4972 | // purpose with or without fee is hereby granted, provided that the above 4973 | // copyright notice and this permission notice appear in all copies. 4974 | // 4975 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 4976 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 4977 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 4978 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 4979 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 4980 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 4981 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 4982 | 4983 | #ifndef NO_PROC_POLL 4984 | 4985 | #if defined(__linux__) 4986 | 4987 | static char * 4988 | getproc(int fd, char *tty) { 4989 | FILE *f; 4990 | char *path, *buf; 4991 | size_t len; 4992 | int ch; 4993 | pid_t pgrp; 4994 | int r; 4995 | 4996 | if ((pgrp = tcgetpgrp(fd)) == -1) { 4997 | return NULL; 4998 | } 4999 | 5000 | r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp); 5001 | if (r == -1 || path == NULL) return NULL; 5002 | 5003 | if ((f = fopen(path, "r")) == NULL) { 5004 | free(path); 5005 | return NULL; 5006 | } 5007 | 5008 | free(path); 5009 | 5010 | len = 0; 5011 | buf = NULL; 5012 | while ((ch = fgetc(f)) != EOF) { 5013 | if (ch == '\0') break; 5014 | buf = (char *)realloc(buf, len + 2); 5015 | if (buf == NULL) return NULL; 5016 | buf[len++] = ch; 5017 | } 5018 | 5019 | if (buf != NULL) { 5020 | buf[len] = '\0'; 5021 | } 5022 | 5023 | fclose(f); 5024 | return buf; 5025 | } 5026 | 5027 | #else 5028 | 5029 | static char * 5030 | getproc(int fd, char *tty) { 5031 | return NULL; 5032 | } 5033 | 5034 | #endif 5035 | 5036 | #endif 5037 | -------------------------------------------------------------------------------- /st.info: -------------------------------------------------------------------------------- 1 | # unsupported xterm caps are (getting) commented. 2 | # as soon as they work, uncomment them. 3 | st| simpleterm, 4 | acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 5 | am, 6 | bce, 7 | bel=^G, 8 | blink=\E[5m, 9 | bold=\E[1m, 10 | cbt=\E[Z, 11 | cvvis=\E[?25h, 12 | civis=\E[?25l, 13 | clear=\E[H\E[2J, 14 | cnorm=\E[?12l\E[?25h, 15 | colors#8, 16 | cols#80, 17 | cr=^M, 18 | csr=\E[%i%p1%d;%p2%dr, 19 | cub=\E[%p1%dD, 20 | cub1=^H, 21 | cud1=^J, 22 | cud=\E[%p1%dB, 23 | cuf1=\E[C, 24 | cuf=\E[%p1%dC, 25 | cup=\E[%i%p1%d;%p2%dH, 26 | cuu1=\E[A, 27 | cuu=\E[%p1%dA, 28 | dch=\E[%p1%dP, 29 | dch1=\E[P, 30 | dl=\E[%p1%dM, 31 | dl1=\E[M, 32 | ech=\E[%p1%dX, 33 | ed=\E[J, 34 | el=\E[K, 35 | el1=\E[1K, 36 | enacs=\E)0, 37 | flash=\E[?5h\E[?5l, 38 | fsl=^G, 39 | home=\E[H, 40 | hpa=\E[%i%p1%dG, 41 | hs, 42 | ht=^I, 43 | hts=\EH, 44 | ich=\E[%p1%d@, 45 | il1=\E[L, 46 | il=\E[%p1%dL, 47 | ind=^J, 48 | indn=\E[%p1%dS, 49 | invis=\E[8m, 50 | is2=\E[4l\E>\E[?1034l, 51 | it#8, 52 | kel=\E[1;2F, 53 | ked=\E[1;5F, 54 | ka1=\E[1~, 55 | ka3=\E[5~, 56 | kc1=\E[4~, 57 | kc3=\E[6~, 58 | kbs=\177, 59 | kcbt=\E[Z, 60 | kb2=\EOu, 61 | kcub1=\EOD, 62 | kcud1=\EOB, 63 | kcuf1=\EOC, 64 | kcuu1=\EOA, 65 | kDC=\E[3;2~, 66 | kent=\EOM, 67 | kEND=\E[1;2F, 68 | kIC=\E[2;2~, 69 | kNXT=\E[6;2~, 70 | kPRV=\E[5;2~, 71 | kHOM=\E[1;2H, 72 | kLFT=\E[1;2D, 73 | kRIT=\E[1;2C, 74 | kind=\E[1;2B, 75 | kri=\E[1;2A, 76 | kclr=\E[3;5~, 77 | kdl1=\E[3;2~, 78 | kdch1=\E[3~, 79 | kich1=\E[2~, 80 | kend=\E[4~, 81 | kf1=\EOP, 82 | kf2=\EOQ, 83 | kf3=\EOR, 84 | kf4=\EOS, 85 | kf5=\E[15~, 86 | kf6=\E[17~, 87 | kf7=\E[18~, 88 | kf8=\E[19~, 89 | kf9=\E[20~, 90 | kf10=\E[21~, 91 | kf11=\E[23~, 92 | kf12=\E[24~, 93 | kf13=\E[1;2P, 94 | kf14=\E[1;2Q, 95 | kf15=\E[1;2R, 96 | kf16=\E[1;2S, 97 | kf17=\E[15;2~, 98 | kf18=\E[17;2~, 99 | kf19=\E[18;2~, 100 | kf20=\E[19;2~, 101 | kf21=\E[20;2~, 102 | kf22=\E[21;2~, 103 | kf23=\E[23;2~, 104 | kf24=\E[24;2~, 105 | kf25=\E[1;5P, 106 | kf26=\E[1;5Q, 107 | kf27=\E[1;5R, 108 | kf28=\E[1;5S, 109 | kf29=\E[15;5~, 110 | kf30=\E[17;5~, 111 | kf31=\E[18;5~, 112 | kf32=\E[19;5~, 113 | kf33=\E[20;5~, 114 | kf34=\E[21;5~, 115 | kf35=\E[23;5~, 116 | kf36=\E[24;5~, 117 | kf37=\E[1;6P, 118 | kf38=\E[1;6Q, 119 | kf39=\E[1;6R, 120 | kf40=\E[1;6S, 121 | kf41=\E[15;6~, 122 | kf42=\E[17;6~, 123 | kf43=\E[18;6~, 124 | kf44=\E[19;6~, 125 | kf45=\E[20;6~, 126 | kf46=\E[21;6~, 127 | kf47=\E[23;6~, 128 | kf48=\E[24;6~, 129 | kf49=\E[1;3P, 130 | kf50=\E[1;3Q, 131 | kf51=\E[1;3R, 132 | kf52=\E[1;3S, 133 | kf53=\E[15;3~, 134 | kf54=\E[17;3~, 135 | kf55=\E[18;3~, 136 | kf56=\E[19;3~, 137 | kf57=\E[20;3~, 138 | kf58=\E[21;3~, 139 | kf59=\E[23;3~, 140 | kf60=\E[24;3~, 141 | kf61=\E[1;4P, 142 | kf62=\E[1;4Q, 143 | kf63=\E[1;4R, 144 | khome=\E[1~, 145 | kil1=\E[2;5~, 146 | krmir=\E[2;2~, 147 | kich1=\E[2~, 148 | knp=\E[6~, 149 | kmous=\E[M, 150 | kpp=\E[5~, 151 | lines#24, 152 | mir, 153 | msgr, 154 | ncv#3, 155 | op=\E[39;49m, 156 | pairs#64, 157 | rc=\E8, 158 | rev=\E[7m, 159 | ri=\EM, 160 | ritm=\E[23m, 161 | rmacs=\E(B, 162 | rmcup=\E[?1049l, 163 | rmir=\E[4l, 164 | rmkx=\E[?1l\E>, 165 | rmm=\E[?1034l, 166 | rmso=\E[27m, 167 | rmul=\E[m, 168 | rs1=\Ec, 169 | rs2=\E[4l\E>\E[?1034l, 170 | sc=\E7, 171 | setab=\E[4%p1%dm, 172 | setaf=\E[3%p1%dm, 173 | setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 174 | setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 175 | sgr0=\E[0m, 176 | sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, 177 | sitm=\E[3m, 178 | smacs=\E(0, 179 | smcup=\E[?1049h, 180 | smir=\E[4h, 181 | smkx=\E[?1h\E=, 182 | smm=\E[?1034h, 183 | smso=\E[7m, 184 | smul=\E[4m, 185 | tbc=\E[3g, 186 | tsl=\E]0;, 187 | ul, 188 | xenl, 189 | vpa=\E[%i%p1%dd, 190 | 191 | 192 | st-256color| simpleterm with 256 colors, 193 | use=st, 194 | colors#256, 195 | pairs#32767, 196 | # Nicked from xterm-256color 197 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 198 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 199 | --------------------------------------------------------------------------------