├── LICENSE ├── Makefile ├── PKGBUILD ├── README.md ├── arg.h ├── config.h ├── config.mk ├── st ├── st-copyout ├── st.1 ├── st.c ├── st.h ├── st.info ├── st.o ├── win.h ├── x.c └── x.o /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2014-2018 Hiltjo Posthuma 4 | © 2018 Devin J. Pohly 5 | © 2014-2017 Quentin Rameau 6 | © 2009-2012 Aurélien APTEL 7 | © 2008-2017 Anselm R Garbe 8 | © 2012-2017 Roberto E. Vargas Caballero 9 | © 2012-2016 Christoph Lohmann <20h at r-36 dot net> 10 | © 2013 Eon S. Jeon 11 | © 2013 Alexander Sedov 12 | © 2013 Mark Edgar 13 | © 2013-2014 Eric Pruitt 14 | © 2013 Michael Forney 15 | © 2013-2014 Markus Teich 16 | © 2014-2015 Laslo Hunhold 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a 19 | copy of this software and associated documentation files (the "Software"), 20 | to deal in the Software without restriction, including without limitation 21 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 31 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 34 | DEALINGS IN THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # st - simple terminal 2 | # See LICENSE file for copyright and license details. 3 | .POSIX: 4 | 5 | include config.mk 6 | 7 | SRC = st.c x.c 8 | OBJ = $(SRC:.c=.o) 9 | 10 | all: options st 11 | 12 | options: 13 | @echo st build options: 14 | @echo "CFLAGS = $(STCFLAGS)" 15 | @echo "LDFLAGS = $(STLDFLAGS)" 16 | @echo "CC = $(CC)" 17 | 18 | config.h: 19 | cp config.def.h config.h 20 | 21 | .c.o: 22 | $(CC) $(STCFLAGS) -c $< 23 | 24 | st.o: config.h st.h win.h 25 | x.o: arg.h config.h st.h win.h 26 | 27 | $(OBJ): config.h config.mk 28 | 29 | st: $(OBJ) 30 | $(CC) -o $@ $(OBJ) $(STLDFLAGS) 31 | 32 | clean: 33 | rm -f st $(OBJ) st-$(VERSION).tar.gz 34 | 35 | dist: clean 36 | mkdir -p st-$(VERSION) 37 | cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 38 | config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 39 | st-$(VERSION) 40 | tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 41 | rm -rf st-$(VERSION) 42 | 43 | install: st 44 | mkdir -p $(DESTDIR)$(PREFIX)/bin 45 | cp -f st $(DESTDIR)$(PREFIX)/bin 46 | cp -f st-copyout $(DESTDIR)$(PREFIX)/bin 47 | chmod 755 $(DESTDIR)$(PREFIX)/bin/st 48 | chmod 755 $(DESTDIR)$(PREFIX)/bin/st-copyout 49 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 50 | sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 51 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 52 | tic -sx st.info 53 | @echo Please see the README file regarding the terminfo entry of st. 54 | 55 | uninstall: 56 | rm -f $(DESTDIR)$(PREFIX)/bin/st 57 | rm -f $(DESTDIR)$(PREFIX)/bin/st-copyout 58 | rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 59 | 60 | .PHONY: all options clean dist install uninstall 61 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: 2 | 3 | pkgname=st-luke-git 4 | _pkgname=st 5 | pkgver=0.8.2.r1062.2087ab9 6 | pkgrel=1 7 | epoch=1 8 | pkgdesc="Luke's simple (suckless) terminal with vim-bindings, transparency, xresources, etc. " 9 | url='https://github.com/LukeSmithxyz/st' 10 | arch=('i686' 'x86_64') 11 | license=('MIT') 12 | options=('zipman') 13 | depends=('libxft') 14 | makedepends=('ncurses' 'libxext' 'git') 15 | optdepends=('dmenu: feed urls to dmenu') 16 | source=('git://github.com/LukeSmithxyz/st') 17 | sha1sums=('SKIP') 18 | 19 | provides=("${_pkgname}") 20 | conflicts=("${_pkgname}") 21 | 22 | pkgver() { 23 | cd "${_pkgname}" 24 | printf "%s.r%s.%s" "$(awk '/^VERSION =/ {print $3}' config.mk)" \ 25 | "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 26 | } 27 | 28 | prepare() { 29 | cd $srcdir/${_pkgname} 30 | # skip terminfo which conflicts with ncurses 31 | sed -i '/tic /d' Makefile 32 | } 33 | 34 | build() { 35 | cd "${_pkgname}" 36 | make X11INC=/usr/include/X11 X11LIB=/usr/lib/X11 37 | } 38 | 39 | package() { 40 | cd "${_pkgname}" 41 | make PREFIX=/usr DESTDIR="${pkgdir}" install 42 | install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" 43 | install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md" 44 | install -Dm644 .Xdefaults "${pkgdir}/usr/share/doc/${pkgname}/Xdefaults.example" 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luke's build of st - the simple (suckless) terminal 2 | 3 | The [suckless terminal (st)](https://st.suckless.org/) with some additional features that make it literally the best terminal emulator ever: 4 | 5 | ## Unique features (using dmenu) 6 | 7 | + **follow urls** by pressing `alt-l` 8 | + **copy urls** in the same way with `alt-y` 9 | + **copy the output of commands** with `alt-o` 10 | 11 | ## Bindings for 12 | 13 | + **scrollback** with `alt-↑/↓` or `alt-pageup/down` or `shift` while scrolling the mouse 14 | + OR **vim-bindings**: scroll up/down in history with `alt-k` and `alt-j`. Faster with `alt-u`/`alt-d`. 15 | + **zoom/change font size**: same bindings as above, but holding down shift as well. `alt-home` returns to default 16 | + **copy text** with `alt-c`, **paste** is `alt-v` or `shift-insert` 17 | 18 | ## Pretty stuff 19 | 20 | + Compatibility with `Xresources` and `pywal` for dynamic colors. The `Xdefaults` file shows a usage example. 21 | + Default [gruvbox](https://github.com/morhetz/gruvbox) colors otherwise. 22 | + Transparency/alpha, which is also adjustable from your `Xresources`. 23 | + Default font is system "mono" at 16pt, meaning the font will match your system font. 24 | 25 | ## Other st patches 26 | 27 | + Vertcenter 28 | + Scrollback 29 | + updated to latest version 0.8.2 30 | 31 | ## Installation for newbs 32 | 33 | ``` 34 | git clone https://github.com/LukeSmithxyz/st 35 | cd st 36 | sudo make install 37 | ``` 38 | 39 | Users of Arch-based distros can also install it from the AUR as [st-luke-git](https://aur.archlinux.org/packages/st-luke-git/). 40 | 41 | Obviously, `make` is required to build. `fontconfig` is required for the default build, since it asks `fontconfig` for your system monospace font. It might be obvious, but `libX11` and `libXft` are required as well. Chances are, you have all of this installed already. 42 | 43 | On OpenBSD, be sure to edit `config.mk` first and remove `-lrt` from the `$LIBS` before compiling. 44 | 45 | Be sure to have a composite manager (`xcompmgr`, `compton`, etc.) running if you want transparency. 46 | 47 | ## How to configure dynamically with Xresources 48 | 49 | For many key variables, this build of `st` will look for X settings set in either `~/.Xdefaults` or `~/.Xresources`. You must run `xrdb` on one of these files to load the settings. 50 | 51 | For example, you can define your desired fonts, transparency or colors: 52 | 53 | ``` 54 | *.font: Liberation Mono:pixelsize=12:antialias=true:autohint=true; 55 | *.alpha: 0.9 56 | *.color0: #111 57 | ... 58 | ``` 59 | 60 | The `alpha` value (for transparency) goes from `0` (transparent) to `1` (opaque). 61 | 62 | ### Colors 63 | 64 | To be clear about the color settings: 65 | 66 | - This build will use gruvbox colors by default and as a fallback. 67 | - If there are Xresources colors defined, those will take priority. 68 | - But if `wal` has run in your session, its colors will take priority. 69 | 70 | Note that when you run `wal`, it will negate the transparency of existing windows, but new windows will continue with the previously defined transparency. 71 | 72 | ## Contact 73 | 74 | - Luke Smith 75 | - [https://lukesmith.xyz](https://lukesmith.xyz) 76 | -------------------------------------------------------------------------------- /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 | /* use main(int argc, char *argv[]) */ 12 | #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ 13 | argv[0] && argv[0][0] == '-'\ 14 | && argv[0][1];\ 15 | argc--, argv++) {\ 16 | char argc_;\ 17 | char **argv_;\ 18 | int brk_;\ 19 | if (argv[0][1] == '-' && argv[0][2] == '\0') {\ 20 | argv++;\ 21 | argc--;\ 22 | break;\ 23 | }\ 24 | int i_;\ 25 | for (i_ = 1, brk_ = 0, argv_ = argv;\ 26 | argv[0][i_] && !brk_;\ 27 | i_++) {\ 28 | if (argv_ != argv)\ 29 | break;\ 30 | argc_ = argv[0][i_];\ 31 | switch (argc_) 32 | 33 | #define ARGEND }\ 34 | } 35 | 36 | #define ARGC() argc_ 37 | 38 | #define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ 39 | ((x), abort(), (char *)0) :\ 40 | (brk_ = 1, (argv[0][i_+1] != '\0')?\ 41 | (&argv[0][i_+1]) :\ 42 | (argc--, argv++, argv[0]))) 43 | 44 | #define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ 45 | (char *)0 :\ 46 | (brk_ = 1, (argv[0][i_+1] != '\0')?\ 47 | (&argv[0][i_+1]) :\ 48 | (argc--, argv++, argv[0]))) 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /config.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 | static char *font = "mono:pixelsize=14:antialias=true:autohint=true"; 9 | static int borderpx = 2; 10 | 11 | /* 12 | * What program is execed by st depends of these precedence rules: 13 | * 1: program passed with -e 14 | * 2: utmp option 15 | * 3: SHELL environment variable 16 | * 4: value of shell in /etc/passwd 17 | * 5: value of shell in config.h 18 | */ 19 | static char *shell = "/bin/sh"; 20 | char *utmp = NULL; 21 | char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 22 | 23 | /* identification sequence returned in DA and DECID */ 24 | char *vtiden = "\033[?6c"; 25 | 26 | /* Kerning / character bounding-box multipliers */ 27 | static float cwscale = 1.0; 28 | static float chscale = 1.0; 29 | 30 | /* 31 | * word delimiter string 32 | * 33 | * More advanced example: L" `'\"()[]{}" 34 | */ 35 | wchar_t *worddelimiters = L" "; 36 | 37 | /* selection timeouts (in milliseconds) */ 38 | static unsigned int doubleclicktimeout = 300; 39 | static unsigned int tripleclicktimeout = 600; 40 | 41 | /* alt screens */ 42 | int allowaltscreen = 1; 43 | 44 | /* frames per second st should at maximum draw to the screen */ 45 | static unsigned int xfps = 120; 46 | static unsigned int actionfps = 30; 47 | 48 | /* 49 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 50 | * attribute. 51 | */ 52 | static unsigned int blinktimeout = 800; 53 | 54 | /* 55 | * thickness of underline and bar cursors 56 | */ 57 | static unsigned int cursorthickness = 2; 58 | 59 | /* 60 | * bell volume. It must be a value between -100 and 100. Use 0 for disabling 61 | * it 62 | */ 63 | static int bellvolume = 0; 64 | 65 | /* default TERM value */ 66 | char *termname = "st-256color"; 67 | 68 | /* 69 | * spaces per tab 70 | * 71 | * When you are changing this value, don't forget to adapt the »it« value in 72 | * the st.info and appropriately install the st.info in the environment where 73 | * you use this st version. 74 | * 75 | * it#$tabspaces, 76 | * 77 | * Secondly make sure your kernel is not expanding tabs. When running `stty 78 | * -a` »tab0« should appear. You can tell the terminal to not expand tabs by 79 | * running following command: 80 | * 81 | * stty tabs 82 | */ 83 | unsigned int tabspaces = 8; 84 | 85 | /* bg opacity */ 86 | float alpha = 0.92; 87 | 88 | /* Terminal colors (16 first used in escape sequence) */ 89 | static const char *colorname[] = { 90 | "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ 91 | "#cc241d", 92 | "#98971a", 93 | "#d79921", 94 | "#458588", 95 | "#b16286", 96 | "#689d6a", 97 | "#a89984", 98 | "#928374", 99 | "#fb4934", 100 | "#b8bb26", 101 | "#fabd2f", 102 | "#83a598", 103 | "#d3869b", 104 | "#8ec07c", 105 | "#ebdbb2", 106 | [255] = 0, 107 | /* more colors can be added after 255 to use with DefaultXX */ 108 | "#282828", /* 256 -> bg */ 109 | "#ebdbb2", /* 257 -> fg */ 110 | "#add8e6", /* 258 -> cursor */ 111 | }; 112 | 113 | 114 | /* 115 | * Default colors (colorname index) 116 | * foreground, background, cursor, reverse cursor 117 | */ 118 | unsigned int defaultfg = 257; 119 | unsigned int defaultbg = 256; 120 | static unsigned int defaultcs = 258; 121 | static unsigned int defaultrcs = 0; 122 | 123 | /* 124 | * Default shape of cursor 125 | * 2: Block ("█") 126 | * 4: Underline ("_") 127 | * 6: Bar ("|") 128 | * 7: Snowman ("☃") 129 | */ 130 | static unsigned int cursorshape = 2; 131 | 132 | /* 133 | * Default columns and rows numbers 134 | */ 135 | 136 | static unsigned int cols = 80; 137 | static unsigned int rows = 24; 138 | 139 | /* 140 | * Default colour and shape of the mouse cursor 141 | */ 142 | static unsigned int mouseshape = XC_xterm; 143 | static unsigned int mousefg = 7; 144 | static unsigned int mousebg = 0; 145 | 146 | /* 147 | * Color used to display font attributes when fontconfig selected a font which 148 | * doesn't match the ones requested. 149 | */ 150 | static unsigned int defaultattr = 11; 151 | 152 | /* 153 | * Xresources preferences to load at startup 154 | */ 155 | ResourcePref resources[] = { 156 | { "font", STRING, &font }, 157 | { "color0", STRING, &colorname[0] }, 158 | { "color1", STRING, &colorname[1] }, 159 | { "color2", STRING, &colorname[2] }, 160 | { "color3", STRING, &colorname[3] }, 161 | { "color4", STRING, &colorname[4] }, 162 | { "color5", STRING, &colorname[5] }, 163 | { "color6", STRING, &colorname[6] }, 164 | { "color7", STRING, &colorname[7] }, 165 | { "color8", STRING, &colorname[8] }, 166 | { "color9", STRING, &colorname[9] }, 167 | { "color10", STRING, &colorname[10] }, 168 | { "color11", STRING, &colorname[11] }, 169 | { "color12", STRING, &colorname[12] }, 170 | { "color13", STRING, &colorname[13] }, 171 | { "color14", STRING, &colorname[14] }, 172 | { "color15", STRING, &colorname[15] }, 173 | { "background", STRING, &colorname[256] }, 174 | { "foreground", STRING, &colorname[257] }, 175 | { "cursorColor", STRING, &colorname[258] }, 176 | { "termname", STRING, &termname }, 177 | { "shell", STRING, &shell }, 178 | { "xfps", INTEGER, &xfps }, 179 | { "actionfps", INTEGER, &actionfps }, 180 | { "blinktimeout", INTEGER, &blinktimeout }, 181 | { "bellvolume", INTEGER, &bellvolume }, 182 | { "tabspaces", INTEGER, &tabspaces }, 183 | { "borderpx", INTEGER, &borderpx }, 184 | { "cwscale", FLOAT, &cwscale }, 185 | { "chscale", FLOAT, &chscale }, 186 | { "alpha", FLOAT, &alpha }, 187 | }; 188 | 189 | /* 190 | * Internal mouse shortcuts. 191 | * Beware that overloading Button1 will disable the selection. 192 | */ 193 | static MouseShortcut mshortcuts[] = { 194 | /* button mask string */ 195 | { Button4, XK_NO_MOD, "\031" }, 196 | { Button5, XK_NO_MOD, "\005" }, 197 | }; 198 | 199 | /* Internal keyboard shortcuts. */ 200 | #define MODKEY Mod1Mask 201 | #define TERMMOD (Mod1Mask|ShiftMask) 202 | 203 | MouseKey mkeys[] = { 204 | /* button mask function argument */ 205 | { Button4, ShiftMask, kscrollup, {.i = 1} }, 206 | { Button5, ShiftMask, kscrolldown, {.i = 1} }, 207 | { Button4, MODKEY, kscrollup, {.i = 1} }, 208 | { Button5, MODKEY, kscrolldown, {.i = 1} }, 209 | { Button4, TERMMOD, zoom, {.f = +1} }, 210 | { Button5, TERMMOD, zoom, {.f = -1} }, 211 | }; 212 | 213 | static char *openurlcmd[] = { "/bin/sh", "-c", 214 | "sed 's/.*│//g' | tr -d '\n' | grep -aEo '(((http|https)://|www\\.)[a-zA-Z0-9.]*[:]?[a-zA-Z0-9./&%?=_-]*)|((magnet:\\?xt=urn:btih:)[a-zA-Z0-9]*)'| uniq | sed 's/^www./http:\\/\\/www\\./g' | dmenu -p 'Follow which url?' -l 10 | xargs -r xdg-open", 215 | "externalpipe", NULL }; 216 | 217 | static char *copyurlcmd[] = { "/bin/sh", "-c", 218 | "sed 's/.*│//g' | tr -d '\n' | grep -aEo '(((http|https)://|www\\.)[a-zA-Z0-9.]*[:]?[a-zA-Z0-9./&%?=_-]*)|((magnet:\\?xt=urn:btih:)[a-zA-Z0-9]*)' | uniq | sed 's/^www./http:\\/\\/www\\./g' | dmenu -p 'Copy which url?' -l 10 | tr -d '\n' | xclip -selection clipboard", 219 | "externalpipe", NULL }; 220 | 221 | static char *copyoutput[] = { "/bin/sh", "-c", "st-copyout", "externalpipe", NULL }; 222 | 223 | static Shortcut shortcuts[] = { 224 | /* mask keysym function argument */ 225 | { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 226 | { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 227 | { ShiftMask, XK_Print, printscreen, {.i = 0} }, 228 | { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, 229 | { TERMMOD, XK_Prior, zoom, {.f = +1} }, 230 | { TERMMOD, XK_Next, zoom, {.f = -1} }, 231 | { MODKEY, XK_Home, zoomreset, {.f = 0} }, 232 | { ShiftMask, XK_Insert, clippaste, {.i = 0} }, 233 | { MODKEY, XK_c, clipcopy, {.i = 0} }, 234 | { MODKEY, XK_v, clippaste, {.i = 0} }, 235 | { MODKEY, XK_p, selpaste, {.i = 0} }, 236 | { MODKEY, XK_Num_Lock, numlock, {.i = 0} }, 237 | { MODKEY, XK_Control_L, iso14755, {.i = 0} }, 238 | { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 239 | { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 240 | { MODKEY, XK_Page_Up, kscrollup, {.i = -1} }, 241 | { MODKEY, XK_Page_Down, kscrolldown, {.i = -1} }, 242 | { MODKEY, XK_k, kscrollup, {.i = 1} }, 243 | { MODKEY, XK_j, kscrolldown, {.i = 1} }, 244 | { MODKEY, XK_Up, kscrollup, {.i = 1} }, 245 | { MODKEY, XK_Down, kscrolldown, {.i = 1} }, 246 | { MODKEY, XK_u, kscrollup, {.i = -1} }, 247 | { MODKEY, XK_d, kscrolldown, {.i = -1} }, 248 | { TERMMOD, XK_Up, zoom, {.f = +1} }, 249 | { TERMMOD, XK_Down, zoom, {.f = -1} }, 250 | { TERMMOD, XK_K, zoom, {.f = +1} }, 251 | { TERMMOD, XK_J, zoom, {.f = -1} }, 252 | { TERMMOD, XK_U, zoom, {.f = +2} }, 253 | { TERMMOD, XK_D, zoom, {.f = -2} }, 254 | { MODKEY, XK_l, externalpipe, {.v = openurlcmd } }, 255 | { MODKEY, XK_y, externalpipe, {.v = copyurlcmd } }, 256 | { MODKEY, XK_o, externalpipe, {.v = copyoutput } }, 257 | }; 258 | 259 | /* 260 | * Special keys (change & recompile st.info accordingly) 261 | * 262 | * Mask value: 263 | * * Use XK_ANY_MOD to match the key no matter modifiers state 264 | * * Use XK_NO_MOD to match the key alone (no modifiers) 265 | * appkey value: 266 | * * 0: no value 267 | * * > 0: keypad application mode enabled 268 | * * = 2: term.numlock = 1 269 | * * < 0: keypad application mode disabled 270 | * appcursor value: 271 | * * 0: no value 272 | * * > 0: cursor application mode enabled 273 | * * < 0: cursor application mode disabled 274 | * crlf value 275 | * * 0: no value 276 | * * > 0: crlf mode is enabled 277 | * * < 0: crlf mode is disabled 278 | * 279 | * Be careful with the order of the definitions because st searches in 280 | * this table sequentially, so any XK_ANY_MOD must be in the last 281 | * position for a key. 282 | */ 283 | 284 | /* 285 | * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) 286 | * to be mapped below, add them to this array. 287 | */ 288 | static KeySym mappedkeys[] = { -1 }; 289 | 290 | /* 291 | * State bits to ignore when matching key or button events. By default, 292 | * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. 293 | */ 294 | static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; 295 | 296 | /* 297 | * Override mouse-select while mask is active (when MODE_MOUSE is set). 298 | * Note that if you want to use ShiftMask with selmasks, set this to an other 299 | * modifier, set to 0 to not use it. 300 | */ 301 | static uint forceselmod = ShiftMask; 302 | 303 | /* 304 | * This is the huge key array which defines all compatibility to the Linux 305 | * world. Please decide about changes wisely. 306 | */ 307 | static Key key[] = { 308 | /* keysym mask string appkey appcursor */ 309 | { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, 310 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, 311 | { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, 312 | { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 313 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, 314 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, 315 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, 316 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, 317 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, 318 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, 319 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, 320 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, 321 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, 322 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, 323 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, 324 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, 325 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, 326 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 327 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, 328 | { XK_KP_End, ControlMask, "\033[J", -1, 0}, 329 | { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, 330 | { XK_KP_End, ShiftMask, "\033[K", -1, 0}, 331 | { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, 332 | { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, 333 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, 334 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 335 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, 336 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, 337 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, 338 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, 339 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 340 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 341 | { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, 342 | { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, 343 | { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, 344 | { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, 345 | { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 346 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 347 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, 348 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, 349 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, 350 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, 351 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, 352 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, 353 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, 354 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, 355 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, 356 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, 357 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, 358 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, 359 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, 360 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, 361 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, 362 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, 363 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, 364 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, 365 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, 366 | { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, 367 | { XK_Up, ControlMask, "\033[1;5A", 0, 0}, 368 | { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, 369 | { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, 370 | { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, 371 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, 372 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, 373 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, 374 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, 375 | { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, 376 | { XK_Down, ControlMask, "\033[1;5B", 0, 0}, 377 | { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, 378 | { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, 379 | { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, 380 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, 381 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, 382 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, 383 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, 384 | { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, 385 | { XK_Left, ControlMask, "\033[1;5D", 0, 0}, 386 | { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, 387 | { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, 388 | { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, 389 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, 390 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, 391 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, 392 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, 393 | { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, 394 | { XK_Right, ControlMask, "\033[1;5C", 0, 0}, 395 | { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, 396 | { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, 397 | { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, 398 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, 399 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, 400 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, 401 | { XK_Return, Mod1Mask, "\033\r", 0, 0}, 402 | { XK_Return, XK_ANY_MOD, "\r", 0, 0}, 403 | { XK_Insert, ShiftMask, "\033[4l", -1, 0}, 404 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, 405 | { XK_Insert, ControlMask, "\033[L", -1, 0}, 406 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, 407 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 408 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 409 | { XK_Delete, ControlMask, "\033[M", -1, 0}, 410 | { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, 411 | { XK_Delete, ShiftMask, "\033[2K", -1, 0}, 412 | { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, 413 | { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 414 | { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 415 | { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, 416 | { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, 417 | { XK_Home, ShiftMask, "\033[2J", 0, -1}, 418 | { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, 419 | { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, 420 | { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 421 | { XK_End, ControlMask, "\033[J", -1, 0}, 422 | { XK_End, ControlMask, "\033[1;5F", +1, 0}, 423 | { XK_End, ShiftMask, "\033[K", -1, 0}, 424 | { XK_End, ShiftMask, "\033[1;2F", +1, 0}, 425 | { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, 426 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, 427 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, 428 | { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 429 | { XK_Next, ControlMask, "\033[6;5~", 0, 0}, 430 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, 431 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 432 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, 433 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, 434 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, 435 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, 436 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, 437 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, 438 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, 439 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, 440 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, 441 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, 442 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, 443 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, 444 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, 445 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, 446 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, 447 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, 448 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, 449 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, 450 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, 451 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, 452 | { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, 453 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, 454 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, 455 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, 456 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, 457 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, 458 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, 459 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, 460 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, 461 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, 462 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, 463 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, 464 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, 465 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, 466 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, 467 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, 468 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, 469 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, 470 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, 471 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, 472 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, 473 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, 474 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, 475 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, 476 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, 477 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, 478 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, 479 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, 480 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, 481 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, 482 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, 483 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, 484 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, 485 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, 486 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, 487 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, 488 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, 489 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, 490 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, 491 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, 492 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, 493 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, 494 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, 495 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, 496 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, 497 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, 498 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, 499 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, 500 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, 501 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, 502 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, 503 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, 504 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, 505 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, 506 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, 507 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, 508 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, 509 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, 510 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, 511 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, 512 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, 513 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, 514 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, 515 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, 516 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, 517 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, 518 | }; 519 | 520 | /* 521 | * Selection types' masks. 522 | * Use the same masks as usual. 523 | * Button1Mask is always unset, to make masks match between ButtonPress. 524 | * ButtonRelease and MotionNotify. 525 | * If no match is found, regular selection is used. 526 | */ 527 | static uint selmasks[] = { 528 | [SEL_RECTANGULAR] = Mod1Mask, 529 | }; 530 | 531 | /* 532 | * Printable characters in ASCII, used to estimate the advance width 533 | * of single wide characters. 534 | */ 535 | static char ascii_printable[] = 536 | " !\"#$%&'()*+,-./0123456789:;<=>?" 537 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 538 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 539 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # st version 2 | VERSION = 0.8.2 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 | PKG_CONFIG = pkg-config 14 | 15 | # includes and libs 16 | INCS = -I$(X11INC) \ 17 | `$(PKG_CONFIG) --cflags fontconfig` \ 18 | `$(PKG_CONFIG) --cflags freetype2` 19 | LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ 20 | `$(PKG_CONFIG) --libs fontconfig` \ 21 | `$(PKG_CONFIG) --libs freetype2` 22 | 23 | # flags 24 | STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 25 | STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) 26 | STLDFLAGS = $(LIBS) $(LDFLAGS) 27 | 28 | # OpenBSD: 29 | #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE 30 | #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ 31 | # `pkg-config --libs fontconfig` \ 32 | # `pkg-config --libs freetype2` 33 | 34 | # compiler and linker 35 | # CC = c99 36 | -------------------------------------------------------------------------------- /st: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MentalOutlaw/st/ac9aaa89114e801335b836c263cbf43fa16393cb/st -------------------------------------------------------------------------------- /st-copyout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Using external pipe with st, give a dmenu prompt of recent commands, 3 | # allowing the user to copy the output of one. 4 | # xclip required for this script. 5 | # By Jaywalker and Luke 6 | tmpfile=$(mktemp /tmp/st-cmd-output.XXXXXX) 7 | trap 'rm "$tmpfile"' 0 1 15 8 | sed -n "w $tmpfile" 9 | ps1="$(grep "\S" "$tmpfile" | tail -n 1 | cut -d' ' -f1)" 10 | chosen="$(grep -F "$ps1" "$tmpfile" | sed '$ d' | tac | dmenu -p "Copy which command's output?" -i -l 10 | sed 's/[^^]/[&]/g; s/\^/\\^/g')" 11 | eps1="$(echo "$ps1" | sed 's/[^^]/[&]/g; s/\^/\\^/g')" 12 | awk "/^$chosen$/{p=1;print;next} p&&/^$eps1/{p=0};p" "$tmpfile" | xclip -selection clipboard 13 | -------------------------------------------------------------------------------- /st.1: -------------------------------------------------------------------------------- 1 | .TH ST 1 st\-VERSION 2 | .SH NAME 3 | st \- simple terminal (Luke Smith (https://lukesmith.xyz)'s build) 4 | .SH SYNOPSIS 5 | .B st 6 | .RB [ \-aiv ] 7 | .RB [ \-c 8 | .IR class ] 9 | .RB [ \-f 10 | .IR font ] 11 | .RB [ \-g 12 | .IR geometry ] 13 | .RB [ \-n 14 | .IR name ] 15 | .RB [ \-o 16 | .IR iofile ] 17 | .RB [ \-T 18 | .IR title ] 19 | .RB [ \-t 20 | .IR title ] 21 | .RB [ \-l 22 | .IR line ] 23 | .RB [ \-w 24 | .IR windowid ] 25 | .RB [[ \-e ] 26 | .IR command 27 | .RI [ arguments ...]] 28 | .PP 29 | .B st 30 | .RB [ \-aiv ] 31 | .RB [ \-c 32 | .IR class ] 33 | .RB [ \-f 34 | .IR font ] 35 | .RB [ \-g 36 | .IR geometry ] 37 | .RB [ \-n 38 | .IR name ] 39 | .RB [ \-o 40 | .IR iofile ] 41 | .RB [ \-T 42 | .IR title ] 43 | .RB [ \-t 44 | .IR title ] 45 | .RB [ \-w 46 | .IR windowid ] 47 | .RB \-l 48 | .IR line 49 | .RI [ stty_args ...] 50 | .SH DESCRIPTION 51 | .B st 52 | is a simple terminal emulator. 53 | .SH OPTIONS 54 | .TP 55 | .B \-a 56 | disable alternate screens in terminal 57 | .TP 58 | .BI \-c " class" 59 | defines the window class (default $TERM). 60 | .TP 61 | .BI \-f " font" 62 | defines the 63 | .I font 64 | to use when st is run. 65 | .TP 66 | .BI \-g " geometry" 67 | defines the X11 geometry string. 68 | The form is [=][{xX}][{+-}{+-}]. See 69 | .BR XParseGeometry (3) 70 | for further details. 71 | .TP 72 | .B \-i 73 | will fixate the position given with the -g option. 74 | .TP 75 | .BI \-n " name" 76 | defines the window instance name (default $TERM). 77 | .TP 78 | .BI \-o " iofile" 79 | writes all the I/O to 80 | .I iofile. 81 | This feature is useful when recording st sessions. A value of "-" means 82 | standard output. 83 | .TP 84 | .BI \-T " title" 85 | defines the window title (default 'st'). 86 | .TP 87 | .BI \-t " title" 88 | defines the window title (default 'st'). 89 | .TP 90 | .BI \-w " windowid" 91 | embeds st within the window identified by 92 | .I windowid 93 | .TP 94 | .BI \-l " line" 95 | use a tty 96 | .I line 97 | instead of a pseudo terminal. 98 | .I line 99 | should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port 100 | 0). 101 | When this flag is given 102 | remaining arguments are used as flags for 103 | .BR stty(1). 104 | By default st initializes the serial line to 8 bits, no parity, 1 stop bit 105 | and a 38400 baud rate. The speed is set by appending it as last argument 106 | (e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are 107 | .BR stty(1) 108 | flags. If you want to set odd parity on 115200 baud use for example 'st -l 109 | /dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for 110 | example 'st -l /dev/ttyS0 cs7 115200'. See 111 | .BR stty(1) 112 | for more arguments and cases. 113 | .TP 114 | .B \-v 115 | prints version information to stderr, then exits. 116 | .TP 117 | .BI \-e " command " [ " arguments " "... ]" 118 | st executes 119 | .I command 120 | instead of the shell. If this is used it 121 | .B must be the last option 122 | on the command line, as in xterm / rxvt. 123 | This option is only intended for compatibility, 124 | and all the remaining arguments are used as a command 125 | even without it. 126 | .SH SHORTCUTS 127 | .TP 128 | .B Alt-j/k or Alt-Up/Down or Alt-Mouse Wheel 129 | Scroll up/down one line at a time. 130 | .TP 131 | .B Alt-u/d or Alt-Page Up/Page Down 132 | Scroll up/down one screen at a time. 133 | .TP 134 | .B Alt-Shift-k/j or Alt-Shift-Page Up/Page Down or Alt-Shift-Mouse Wheel 135 | Increase or decrease font size. 136 | .TP 137 | .B Alt-Home 138 | Reset to default font size. 139 | .TP 140 | .B Shift-Insert or Alt-v 141 | Paste from clipboard. 142 | .TP 143 | .B Alt-c 144 | Copy to clipboard. 145 | .TP 146 | .B Alt-p 147 | Paste/input primary selection. 148 | .TP 149 | .B Alt-l 150 | Show dmenu menu of all URLs on screen and choose one to open. 151 | .TP 152 | .B Alt-y 153 | Show dmenu menu of all URLs on screen and choose one to copy. 154 | .TP 155 | .B Alt-o 156 | Show dmenu menu of all recently run commands and copy the output of the chosen command to the clipboard. 157 | .I xclip 158 | required. 159 | .TP 160 | .B Break 161 | Send a break in the serial line. 162 | Break key is obtained in PC keyboards 163 | pressing at the same time control and pause. 164 | .TP 165 | .B Ctrl-Print Screen 166 | Toggle if st should print to the 167 | .I iofile. 168 | .TP 169 | .B Shift-Print Screen 170 | Print the full screen to the 171 | .I iofile. 172 | .TP 173 | .B Print Screen 174 | Print the selection to the 175 | .I iofile. 176 | .TP 177 | .B Alt-Ctrl 178 | Launch dmenu to enter a unicode codepoint and send the corresponding glyph 179 | to st. 180 | .SH CUSTOMIZATION 181 | .B st 182 | can be customized by creating a custom config.h and (re)compiling the source 183 | code. This keeps it fast, secure and simple. 184 | .SH AUTHORS 185 | See the LICENSE file for the authors. 186 | .SH LICENSE 187 | See the LICENSE file for the terms of redistribution. 188 | .SH SEE ALSO 189 | .BR tabbed (1), 190 | .BR utmp (1), 191 | .BR stty (1) 192 | .SH BUGS 193 | See the TODO file in the distribution. 194 | 195 | -------------------------------------------------------------------------------- /st.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license 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 | 20 | #include "st.h" 21 | #include "win.h" 22 | 23 | #if defined(__linux) 24 | #include 25 | #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 26 | #include 27 | #elif defined(__FreeBSD__) || defined(__DragonFly__) 28 | #include 29 | #endif 30 | 31 | /* Arbitrary sizes */ 32 | #define UTF_INVALID 0xFFFD 33 | #define UTF_SIZ 4 34 | #define ESC_BUF_SIZ (128*UTF_SIZ) 35 | #define ESC_ARG_SIZ 16 36 | #define STR_BUF_SIZ ESC_BUF_SIZ 37 | #define STR_ARG_SIZ ESC_ARG_SIZ 38 | #define HISTSIZE 2000 39 | 40 | /* macros */ 41 | #define IS_SET(flag) ((term.mode & (flag)) != 0) 42 | #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) 43 | #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') 44 | #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 45 | #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 46 | #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 47 | #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 48 | term.scr + HISTSIZE + 1) % HISTSIZE] : \ 49 | term.line[(y) - term.scr]) 50 | 51 | #define TLINE_HIST(y) ((y) <= HISTSIZE-term.row+2 ? term.hist[(y)] : term.line[(y-HISTSIZE+term.row-3)]) 52 | 53 | /* constants */ 54 | #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: ] [;]] []] */ 149 | typedef struct { 150 | char buf[ESC_BUF_SIZ]; /* raw string */ 151 | int len; /* raw string length */ 152 | char priv; 153 | int arg[ESC_ARG_SIZ]; 154 | int narg; /* nb of args */ 155 | char mode[2]; 156 | } CSIEscape; 157 | 158 | /* STR Escape sequence structs */ 159 | /* ESC type [[ [] [;]] ] ESC '\' */ 160 | typedef struct { 161 | char type; /* ESC type ... */ 162 | char buf[STR_BUF_SIZ]; /* raw string */ 163 | int len; /* raw string length */ 164 | char *args[STR_ARG_SIZ]; 165 | int narg; /* nb of args */ 166 | } STREscape; 167 | 168 | static void execsh(char *, char **); 169 | static void stty(char **); 170 | static void sigchld(int); 171 | static void ttywriteraw(const char *, size_t); 172 | 173 | static void csidump(void); 174 | static void csihandle(void); 175 | static void csiparse(void); 176 | static void csireset(void); 177 | static int eschandle(uchar); 178 | static void strdump(void); 179 | static void strhandle(void); 180 | static void strparse(void); 181 | static void strreset(void); 182 | 183 | static void tprinter(char *, size_t); 184 | static void tdumpsel(void); 185 | static void tdumpline(int); 186 | static void tdump(void); 187 | static void tclearregion(int, int, int, int); 188 | static void tcursor(int); 189 | static void tdeletechar(int); 190 | static void tdeleteline(int); 191 | static void tinsertblank(int); 192 | static void tinsertblankline(int); 193 | static int tlinelen(int); 194 | static void tmoveto(int, int); 195 | static void tmoveato(int, int); 196 | static void tnewline(int); 197 | static void tputtab(int); 198 | static void tputc(Rune); 199 | static void treset(void); 200 | static void tscrollup(int, int, int); 201 | static void tscrolldown(int, int, int); 202 | static void tsetattr(int *, int); 203 | static void tsetchar(Rune, Glyph *, int, int); 204 | static void tsetdirt(int, int); 205 | static void tsetscroll(int, int); 206 | static void tswapscreen(void); 207 | static void tsetmode(int, int, int *, int); 208 | static int twrite(const char *, int, int); 209 | static void tfulldirt(void); 210 | static void tcontrolcode(uchar ); 211 | static void tdectest(char ); 212 | static void tdefutf8(char); 213 | static int32_t tdefcolor(int *, int *, int); 214 | static void tdeftran(char); 215 | static void tstrsequence(uchar); 216 | 217 | static void drawregion(int, int, int, int); 218 | 219 | static void selnormalize(void); 220 | static void selscroll(int, int); 221 | static void selsnap(int *, int *, int); 222 | 223 | static size_t utf8decode(const char *, Rune *, size_t); 224 | static Rune utf8decodebyte(char, size_t *); 225 | static char utf8encodebyte(Rune, size_t); 226 | static size_t utf8validate(Rune *, size_t); 227 | 228 | static char *base64dec(const char *); 229 | static char base64dec_getc(const char **); 230 | 231 | static ssize_t xwrite(int, const char *, size_t); 232 | 233 | /* Globals */ 234 | static Term term; 235 | static Selection sel; 236 | static CSIEscape csiescseq; 237 | static STREscape strescseq; 238 | static int iofd = 1; 239 | static int cmdfd; 240 | static pid_t pid; 241 | 242 | static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 243 | static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 244 | static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 245 | static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 246 | 247 | ssize_t 248 | xwrite(int fd, const char *s, size_t len) 249 | { 250 | size_t aux = len; 251 | ssize_t r; 252 | 253 | while (len > 0) { 254 | r = write(fd, s, len); 255 | if (r < 0) 256 | return r; 257 | len -= r; 258 | s += r; 259 | } 260 | 261 | return aux; 262 | } 263 | 264 | void * 265 | xmalloc(size_t len) 266 | { 267 | void *p; 268 | 269 | if (!(p = malloc(len))) 270 | die("malloc: %s\n", strerror(errno)); 271 | 272 | return p; 273 | } 274 | 275 | void * 276 | xrealloc(void *p, size_t len) 277 | { 278 | if ((p = realloc(p, len)) == NULL) 279 | die("realloc: %s\n", strerror(errno)); 280 | 281 | return p; 282 | } 283 | 284 | char * 285 | xstrdup(char *s) 286 | { 287 | if ((s = strdup(s)) == NULL) 288 | die("strdup: %s\n", strerror(errno)); 289 | 290 | return s; 291 | } 292 | 293 | size_t 294 | utf8decode(const char *c, Rune *u, size_t clen) 295 | { 296 | size_t i, j, len, type; 297 | Rune udecoded; 298 | 299 | *u = UTF_INVALID; 300 | if (!clen) 301 | return 0; 302 | udecoded = utf8decodebyte(c[0], &len); 303 | if (!BETWEEN(len, 1, UTF_SIZ)) 304 | return 1; 305 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 306 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 307 | if (type != 0) 308 | return j; 309 | } 310 | if (j < len) 311 | return 0; 312 | *u = udecoded; 313 | utf8validate(u, len); 314 | 315 | return len; 316 | } 317 | 318 | Rune 319 | utf8decodebyte(char c, size_t *i) 320 | { 321 | for (*i = 0; *i < LEN(utfmask); ++(*i)) 322 | if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 323 | return (uchar)c & ~utfmask[*i]; 324 | 325 | return 0; 326 | } 327 | 328 | size_t 329 | utf8encode(Rune u, char *c) 330 | { 331 | size_t len, i; 332 | 333 | len = utf8validate(&u, 0); 334 | if (len > UTF_SIZ) 335 | return 0; 336 | 337 | for (i = len - 1; i != 0; --i) { 338 | c[i] = utf8encodebyte(u, 0); 339 | u >>= 6; 340 | } 341 | c[0] = utf8encodebyte(u, len); 342 | 343 | return len; 344 | } 345 | 346 | char 347 | utf8encodebyte(Rune u, size_t i) 348 | { 349 | return utfbyte[i] | (u & ~utfmask[i]); 350 | } 351 | 352 | size_t 353 | utf8validate(Rune *u, size_t i) 354 | { 355 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 356 | *u = UTF_INVALID; 357 | for (i = 1; *u > utfmax[i]; ++i) 358 | ; 359 | 360 | return i; 361 | } 362 | 363 | static const char base64_digits[] = { 364 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 365 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 366 | 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 367 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 368 | 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 369 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 370 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 371 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 372 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 373 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 374 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 375 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 376 | }; 377 | 378 | char 379 | base64dec_getc(const char **src) 380 | { 381 | while (**src && !isprint(**src)) (*src)++; 382 | return *((*src)++); 383 | } 384 | 385 | char * 386 | base64dec(const char *src) 387 | { 388 | size_t in_len = strlen(src); 389 | char *result, *dst; 390 | 391 | if (in_len % 4) 392 | in_len += 4 - (in_len % 4); 393 | result = dst = xmalloc(in_len / 4 * 3 + 1); 394 | while (*src) { 395 | int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 396 | int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 397 | int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 398 | int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 399 | 400 | *dst++ = (a << 2) | ((b & 0x30) >> 4); 401 | if (c == -1) 402 | break; 403 | *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 404 | if (d == -1) 405 | break; 406 | *dst++ = ((c & 0x03) << 6) | d; 407 | } 408 | *dst = '\0'; 409 | return result; 410 | } 411 | 412 | void 413 | selinit(void) 414 | { 415 | sel.mode = SEL_IDLE; 416 | sel.snap = 0; 417 | sel.ob.x = -1; 418 | } 419 | 420 | int 421 | tlinelen(int y) 422 | { 423 | int i = term.col; 424 | 425 | if (TLINE(y)[i - 1].mode & ATTR_WRAP) 426 | return i; 427 | 428 | while (i > 0 && TLINE(y)[i - 1].u == ' ') 429 | --i; 430 | 431 | return i; 432 | } 433 | 434 | int 435 | tlinehistlen(int y) 436 | { 437 | int i = term.col; 438 | 439 | if (TLINE_HIST(y)[i - 1].mode & ATTR_WRAP) 440 | return i; 441 | 442 | while (i > 0 && TLINE_HIST(y)[i - 1].u == ' ') 443 | --i; 444 | 445 | return i; 446 | } 447 | 448 | void 449 | selstart(int col, int row, int snap) 450 | { 451 | selclear(); 452 | sel.mode = SEL_EMPTY; 453 | sel.type = SEL_REGULAR; 454 | sel.alt = IS_SET(MODE_ALTSCREEN); 455 | sel.snap = snap; 456 | sel.oe.x = sel.ob.x = col; 457 | sel.oe.y = sel.ob.y = row; 458 | selnormalize(); 459 | 460 | if (sel.snap != 0) 461 | sel.mode = SEL_READY; 462 | tsetdirt(sel.nb.y, sel.ne.y); 463 | } 464 | 465 | void 466 | selextend(int col, int row, int type, int done) 467 | { 468 | int oldey, oldex, oldsby, oldsey, oldtype; 469 | 470 | if (sel.mode == SEL_IDLE) 471 | return; 472 | if (done && sel.mode == SEL_EMPTY) { 473 | selclear(); 474 | return; 475 | } 476 | 477 | oldey = sel.oe.y; 478 | oldex = sel.oe.x; 479 | oldsby = sel.nb.y; 480 | oldsey = sel.ne.y; 481 | oldtype = sel.type; 482 | 483 | sel.oe.x = col; 484 | sel.oe.y = row; 485 | selnormalize(); 486 | sel.type = type; 487 | 488 | if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 489 | tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 490 | 491 | sel.mode = done ? SEL_IDLE : SEL_READY; 492 | } 493 | 494 | void 495 | selnormalize(void) 496 | { 497 | int i; 498 | 499 | if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 500 | sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 501 | sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 502 | } else { 503 | sel.nb.x = MIN(sel.ob.x, sel.oe.x); 504 | sel.ne.x = MAX(sel.ob.x, sel.oe.x); 505 | } 506 | sel.nb.y = MIN(sel.ob.y, sel.oe.y); 507 | sel.ne.y = MAX(sel.ob.y, sel.oe.y); 508 | 509 | selsnap(&sel.nb.x, &sel.nb.y, -1); 510 | selsnap(&sel.ne.x, &sel.ne.y, +1); 511 | 512 | /* expand selection over line breaks */ 513 | if (sel.type == SEL_RECTANGULAR) 514 | return; 515 | i = tlinelen(sel.nb.y); 516 | if (i < sel.nb.x) 517 | sel.nb.x = i; 518 | if (tlinelen(sel.ne.y) <= sel.ne.x) 519 | sel.ne.x = term.col - 1; 520 | } 521 | 522 | int 523 | selected(int x, int y) 524 | { 525 | if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 526 | sel.alt != IS_SET(MODE_ALTSCREEN)) 527 | return 0; 528 | 529 | if (sel.type == SEL_RECTANGULAR) 530 | return BETWEEN(y, sel.nb.y, sel.ne.y) 531 | && BETWEEN(x, sel.nb.x, sel.ne.x); 532 | 533 | return BETWEEN(y, sel.nb.y, sel.ne.y) 534 | && (y != sel.nb.y || x >= sel.nb.x) 535 | && (y != sel.ne.y || x <= sel.ne.x); 536 | } 537 | 538 | void 539 | selsnap(int *x, int *y, int direction) 540 | { 541 | int newx, newy, xt, yt; 542 | int delim, prevdelim; 543 | Glyph *gp, *prevgp; 544 | 545 | switch (sel.snap) { 546 | case SNAP_WORD: 547 | /* 548 | * Snap around if the word wraps around at the end or 549 | * beginning of a line. 550 | */ 551 | prevgp = &TLINE(*y)[*x]; 552 | prevdelim = ISDELIM(prevgp->u); 553 | for (;;) { 554 | newx = *x + direction; 555 | newy = *y; 556 | if (!BETWEEN(newx, 0, term.col - 1)) { 557 | newy += direction; 558 | newx = (newx + term.col) % term.col; 559 | if (!BETWEEN(newy, 0, term.row - 1)) 560 | break; 561 | 562 | if (direction > 0) 563 | yt = *y, xt = *x; 564 | else 565 | yt = newy, xt = newx; 566 | if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 567 | break; 568 | } 569 | 570 | if (newx >= tlinelen(newy)) 571 | break; 572 | 573 | gp = &TLINE(newy)[newx]; 574 | delim = ISDELIM(gp->u); 575 | if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 576 | || (delim && gp->u != prevgp->u))) 577 | break; 578 | 579 | *x = newx; 580 | *y = newy; 581 | prevgp = gp; 582 | prevdelim = delim; 583 | } 584 | break; 585 | case SNAP_LINE: 586 | /* 587 | * Snap around if the the previous line or the current one 588 | * has set ATTR_WRAP at its end. Then the whole next or 589 | * previous line will be selected. 590 | */ 591 | *x = (direction < 0) ? 0 : term.col - 1; 592 | if (direction < 0) { 593 | for (; *y > 0; *y += direction) { 594 | if (!(TLINE(*y-1)[term.col-1].mode 595 | & ATTR_WRAP)) { 596 | break; 597 | } 598 | } 599 | } else if (direction > 0) { 600 | for (; *y < term.row-1; *y += direction) { 601 | if (!(TLINE(*y)[term.col-1].mode 602 | & ATTR_WRAP)) { 603 | break; 604 | } 605 | } 606 | } 607 | break; 608 | } 609 | } 610 | 611 | char * 612 | getsel(void) 613 | { 614 | char *str, *ptr; 615 | int y, bufsize, lastx, linelen; 616 | Glyph *gp, *last; 617 | 618 | if (sel.ob.x == -1) 619 | return NULL; 620 | 621 | bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 622 | ptr = str = xmalloc(bufsize); 623 | 624 | /* append every set & selected glyph to the selection */ 625 | for (y = sel.nb.y; y <= sel.ne.y; y++) { 626 | if ((linelen = tlinelen(y)) == 0) { 627 | *ptr++ = '\n'; 628 | continue; 629 | } 630 | 631 | if (sel.type == SEL_RECTANGULAR) { 632 | gp = &TLINE(y)[sel.nb.x]; 633 | lastx = sel.ne.x; 634 | } else { 635 | gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 636 | lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 637 | } 638 | last = &TLINE(y)[MIN(lastx, linelen-1)]; 639 | while (last >= gp && last->u == ' ') 640 | --last; 641 | 642 | for ( ; gp <= last; ++gp) { 643 | if (gp->mode & ATTR_WDUMMY) 644 | continue; 645 | 646 | ptr += utf8encode(gp->u, ptr); 647 | } 648 | 649 | /* 650 | * Copy and pasting of line endings is inconsistent 651 | * in the inconsistent terminal and GUI world. 652 | * The best solution seems like to produce '\n' when 653 | * something is copied from st and convert '\n' to 654 | * '\r', when something to be pasted is received by 655 | * st. 656 | * FIXME: Fix the computer world. 657 | */ 658 | if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) 659 | *ptr++ = '\n'; 660 | } 661 | *ptr = 0; 662 | return str; 663 | } 664 | 665 | void 666 | selclear(void) 667 | { 668 | if (sel.ob.x == -1) 669 | return; 670 | sel.mode = SEL_IDLE; 671 | sel.ob.x = -1; 672 | tsetdirt(sel.nb.y, sel.ne.y); 673 | } 674 | 675 | void 676 | die(const char *errstr, ...) 677 | { 678 | va_list ap; 679 | 680 | va_start(ap, errstr); 681 | vfprintf(stderr, errstr, ap); 682 | va_end(ap); 683 | exit(1); 684 | } 685 | 686 | void 687 | execsh(char *cmd, char **args) 688 | { 689 | char *sh, *prog; 690 | const struct passwd *pw; 691 | 692 | errno = 0; 693 | if ((pw = getpwuid(getuid())) == NULL) { 694 | if (errno) 695 | die("getpwuid: %s\n", strerror(errno)); 696 | else 697 | die("who are you?\n"); 698 | } 699 | 700 | if ((sh = getenv("SHELL")) == NULL) 701 | sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 702 | 703 | if (args) 704 | prog = args[0]; 705 | else if (utmp) 706 | prog = utmp; 707 | else 708 | prog = sh; 709 | DEFAULT(args, ((char *[]) {prog, NULL})); 710 | 711 | unsetenv("COLUMNS"); 712 | unsetenv("LINES"); 713 | unsetenv("TERMCAP"); 714 | setenv("LOGNAME", pw->pw_name, 1); 715 | setenv("USER", pw->pw_name, 1); 716 | setenv("SHELL", sh, 1); 717 | setenv("HOME", pw->pw_dir, 1); 718 | setenv("TERM", termname, 1); 719 | 720 | signal(SIGCHLD, SIG_DFL); 721 | signal(SIGHUP, SIG_DFL); 722 | signal(SIGINT, SIG_DFL); 723 | signal(SIGQUIT, SIG_DFL); 724 | signal(SIGTERM, SIG_DFL); 725 | signal(SIGALRM, SIG_DFL); 726 | 727 | execvp(prog, args); 728 | _exit(1); 729 | } 730 | 731 | void 732 | sigchld(int a) 733 | { 734 | int stat; 735 | pid_t p; 736 | 737 | if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 738 | die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 739 | 740 | if (pid != p) 741 | return; 742 | 743 | if (WIFEXITED(stat) && WEXITSTATUS(stat)) 744 | die("child exited with status %d\n", WEXITSTATUS(stat)); 745 | else if (WIFSIGNALED(stat)) 746 | die("child terminated due to signal %d\n", WTERMSIG(stat)); 747 | exit(0); 748 | } 749 | 750 | void 751 | stty(char **args) 752 | { 753 | char cmd[_POSIX_ARG_MAX], **p, *q, *s; 754 | size_t n, siz; 755 | 756 | if ((n = strlen(stty_args)) > sizeof(cmd)-1) 757 | die("incorrect stty parameters\n"); 758 | memcpy(cmd, stty_args, n); 759 | q = cmd + n; 760 | siz = sizeof(cmd) - n; 761 | for (p = args; p && (s = *p); ++p) { 762 | if ((n = strlen(s)) > siz-1) 763 | die("stty parameter length too long\n"); 764 | *q++ = ' '; 765 | memcpy(q, s, n); 766 | q += n; 767 | siz -= n + 1; 768 | } 769 | *q = '\0'; 770 | if (system(cmd) != 0) 771 | perror("Couldn't call stty"); 772 | } 773 | 774 | int 775 | ttynew(char *line, char *cmd, char *out, char **args) 776 | { 777 | int m, s; 778 | 779 | if (out) { 780 | term.mode |= MODE_PRINT; 781 | iofd = (!strcmp(out, "-")) ? 782 | 1 : open(out, O_WRONLY | O_CREAT, 0666); 783 | if (iofd < 0) { 784 | fprintf(stderr, "Error opening %s:%s\n", 785 | out, strerror(errno)); 786 | } 787 | } 788 | 789 | if (line) { 790 | if ((cmdfd = open(line, O_RDWR)) < 0) 791 | die("open line '%s' failed: %s\n", 792 | line, strerror(errno)); 793 | dup2(cmdfd, 0); 794 | stty(args); 795 | return cmdfd; 796 | } 797 | 798 | /* seems to work fine on linux, openbsd and freebsd */ 799 | if (openpty(&m, &s, NULL, NULL, NULL) < 0) 800 | die("openpty failed: %s\n", strerror(errno)); 801 | 802 | switch (pid = fork()) { 803 | case -1: 804 | die("fork failed: %s\n", strerror(errno)); 805 | break; 806 | case 0: 807 | close(iofd); 808 | setsid(); /* create a new process group */ 809 | dup2(s, 0); 810 | dup2(s, 1); 811 | dup2(s, 2); 812 | if (ioctl(s, TIOCSCTTY, NULL) < 0) 813 | die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 814 | close(s); 815 | close(m); 816 | #ifdef __OpenBSD__ 817 | if (pledge("stdio getpw proc exec", NULL) == -1) 818 | die("pledge\n"); 819 | #endif 820 | execsh(cmd, args); 821 | break; 822 | default: 823 | #ifdef __OpenBSD__ 824 | if (pledge("stdio rpath tty proc", NULL) == -1) 825 | die("pledge\n"); 826 | #endif 827 | close(s); 828 | cmdfd = m; 829 | signal(SIGCHLD, sigchld); 830 | break; 831 | } 832 | return cmdfd; 833 | } 834 | 835 | size_t 836 | ttyread(void) 837 | { 838 | static char buf[BUFSIZ]; 839 | static int buflen = 0; 840 | int written; 841 | int ret; 842 | 843 | /* append read bytes to unprocessed bytes */ 844 | if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) 845 | die("couldn't read from shell: %s\n", strerror(errno)); 846 | buflen += ret; 847 | 848 | written = twrite(buf, buflen, 0); 849 | buflen -= written; 850 | /* keep any uncomplete utf8 char for the next call */ 851 | if (buflen > 0) 852 | memmove(buf, buf + written, buflen); 853 | 854 | return ret; 855 | } 856 | 857 | void 858 | ttywrite(const char *s, size_t n, int may_echo) 859 | { 860 | const char *next; 861 | Arg arg = (Arg) { .i = term.scr }; 862 | 863 | kscrolldown(&arg); 864 | 865 | if (may_echo && IS_SET(MODE_ECHO)) 866 | twrite(s, n, 1); 867 | 868 | if (!IS_SET(MODE_CRLF)) { 869 | ttywriteraw(s, n); 870 | return; 871 | } 872 | 873 | /* This is similar to how the kernel handles ONLCR for ttys */ 874 | while (n > 0) { 875 | if (*s == '\r') { 876 | next = s + 1; 877 | ttywriteraw("\r\n", 2); 878 | } else { 879 | next = memchr(s, '\r', n); 880 | DEFAULT(next, s + n); 881 | ttywriteraw(s, next - s); 882 | } 883 | n -= next - s; 884 | s = next; 885 | } 886 | } 887 | 888 | void 889 | ttywriteraw(const char *s, size_t n) 890 | { 891 | fd_set wfd, rfd; 892 | ssize_t r; 893 | size_t lim = 256; 894 | 895 | /* 896 | * Remember that we are using a pty, which might be a modem line. 897 | * Writing too much will clog the line. That's why we are doing this 898 | * dance. 899 | * FIXME: Migrate the world to Plan 9. 900 | */ 901 | while (n > 0) { 902 | FD_ZERO(&wfd); 903 | FD_ZERO(&rfd); 904 | FD_SET(cmdfd, &wfd); 905 | FD_SET(cmdfd, &rfd); 906 | 907 | /* Check if we can write. */ 908 | if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 909 | if (errno == EINTR) 910 | continue; 911 | die("select failed: %s\n", strerror(errno)); 912 | } 913 | if (FD_ISSET(cmdfd, &wfd)) { 914 | /* 915 | * Only write the bytes written by ttywrite() or the 916 | * default of 256. This seems to be a reasonable value 917 | * for a serial line. Bigger values might clog the I/O. 918 | */ 919 | if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 920 | goto write_error; 921 | if (r < n) { 922 | /* 923 | * We weren't able to write out everything. 924 | * This means the buffer is getting full 925 | * again. Empty it. 926 | */ 927 | if (n < lim) 928 | lim = ttyread(); 929 | n -= r; 930 | s += r; 931 | } else { 932 | /* All bytes have been written. */ 933 | break; 934 | } 935 | } 936 | if (FD_ISSET(cmdfd, &rfd)) 937 | lim = ttyread(); 938 | } 939 | return; 940 | 941 | write_error: 942 | die("write error on tty: %s\n", strerror(errno)); 943 | } 944 | 945 | void 946 | ttyresize(int tw, int th) 947 | { 948 | struct winsize w; 949 | 950 | w.ws_row = term.row; 951 | w.ws_col = term.col; 952 | w.ws_xpixel = tw; 953 | w.ws_ypixel = th; 954 | if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 955 | fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 956 | } 957 | 958 | void 959 | ttyhangup() 960 | { 961 | /* Send SIGHUP to shell */ 962 | kill(pid, SIGHUP); 963 | } 964 | 965 | int 966 | tattrset(int attr) 967 | { 968 | int i, j; 969 | 970 | for (i = 0; i < term.row-1; i++) { 971 | for (j = 0; j < term.col-1; j++) { 972 | if (term.line[i][j].mode & attr) 973 | return 1; 974 | } 975 | } 976 | 977 | return 0; 978 | } 979 | 980 | void 981 | tsetdirt(int top, int bot) 982 | { 983 | int i; 984 | 985 | LIMIT(top, 0, term.row-1); 986 | LIMIT(bot, 0, term.row-1); 987 | 988 | for (i = top; i <= bot; i++) 989 | term.dirty[i] = 1; 990 | } 991 | 992 | void 993 | tsetdirtattr(int attr) 994 | { 995 | int i, j; 996 | 997 | for (i = 0; i < term.row-1; i++) { 998 | for (j = 0; j < term.col-1; j++) { 999 | if (term.line[i][j].mode & attr) { 1000 | tsetdirt(i, i); 1001 | break; 1002 | } 1003 | } 1004 | } 1005 | } 1006 | 1007 | void 1008 | tfulldirt(void) 1009 | { 1010 | tsetdirt(0, term.row-1); 1011 | } 1012 | 1013 | void 1014 | tcursor(int mode) 1015 | { 1016 | static TCursor c[2]; 1017 | int alt = IS_SET(MODE_ALTSCREEN); 1018 | 1019 | if (mode == CURSOR_SAVE) { 1020 | c[alt] = term.c; 1021 | } else if (mode == CURSOR_LOAD) { 1022 | term.c = c[alt]; 1023 | tmoveto(c[alt].x, c[alt].y); 1024 | } 1025 | } 1026 | 1027 | void 1028 | treset(void) 1029 | { 1030 | uint i; 1031 | 1032 | term.c = (TCursor){{ 1033 | .mode = ATTR_NULL, 1034 | .fg = defaultfg, 1035 | .bg = defaultbg 1036 | }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1037 | 1038 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1039 | for (i = tabspaces; i < term.col; i += tabspaces) 1040 | term.tabs[i] = 1; 1041 | term.top = 0; 1042 | term.bot = term.row - 1; 1043 | term.mode = MODE_WRAP|MODE_UTF8; 1044 | memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1045 | term.charset = 0; 1046 | 1047 | for (i = 0; i < 2; i++) { 1048 | tmoveto(0, 0); 1049 | tcursor(CURSOR_SAVE); 1050 | tclearregion(0, 0, term.col-1, term.row-1); 1051 | tswapscreen(); 1052 | } 1053 | } 1054 | 1055 | void 1056 | tnew(int col, int row) 1057 | { 1058 | term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1059 | tresize(col, row); 1060 | treset(); 1061 | } 1062 | 1063 | void 1064 | tswapscreen(void) 1065 | { 1066 | Line *tmp = term.line; 1067 | 1068 | term.line = term.alt; 1069 | term.alt = tmp; 1070 | term.mode ^= MODE_ALTSCREEN; 1071 | tfulldirt(); 1072 | } 1073 | 1074 | void 1075 | kscrolldown(const Arg* a) 1076 | { 1077 | int n = a->i; 1078 | 1079 | if (n < 0) 1080 | n = term.row + n; 1081 | 1082 | if (n > term.scr) 1083 | n = term.scr; 1084 | 1085 | if (term.scr > 0) { 1086 | term.scr -= n; 1087 | selscroll(0, -n); 1088 | tfulldirt(); 1089 | } 1090 | } 1091 | 1092 | void 1093 | kscrollup(const Arg* a) 1094 | { 1095 | int n = a->i; 1096 | 1097 | if (n < 0) 1098 | n = term.row + n; 1099 | 1100 | if (term.scr <= HISTSIZE-n) { 1101 | term.scr += n; 1102 | selscroll(0, n); 1103 | tfulldirt(); 1104 | } 1105 | } 1106 | 1107 | void 1108 | tscrolldown(int orig, int n, int copyhist) 1109 | { 1110 | int i; 1111 | Line temp; 1112 | 1113 | LIMIT(n, 0, term.bot-orig+1); 1114 | 1115 | if (copyhist) { 1116 | term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1117 | temp = term.hist[term.histi]; 1118 | term.hist[term.histi] = term.line[term.bot]; 1119 | term.line[term.bot] = temp; 1120 | } 1121 | 1122 | tsetdirt(orig, term.bot-n); 1123 | tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1124 | 1125 | for (i = term.bot; i >= orig+n; i--) { 1126 | temp = term.line[i]; 1127 | term.line[i] = term.line[i-n]; 1128 | term.line[i-n] = temp; 1129 | } 1130 | 1131 | selscroll(orig, n); 1132 | } 1133 | 1134 | void 1135 | tscrollup(int orig, int n, int copyhist) 1136 | { 1137 | int i; 1138 | Line temp; 1139 | 1140 | LIMIT(n, 0, term.bot-orig+1); 1141 | 1142 | if (copyhist) { 1143 | term.histi = (term.histi + 1) % HISTSIZE; 1144 | temp = term.hist[term.histi]; 1145 | term.hist[term.histi] = term.line[orig]; 1146 | term.line[orig] = temp; 1147 | } 1148 | 1149 | if (term.scr > 0 && term.scr < HISTSIZE) 1150 | term.scr = MIN(term.scr + n, HISTSIZE-1); 1151 | 1152 | tclearregion(0, orig, term.col-1, orig+n-1); 1153 | tsetdirt(orig+n, term.bot); 1154 | 1155 | for (i = orig; i <= term.bot-n; i++) { 1156 | temp = term.line[i]; 1157 | term.line[i] = term.line[i+n]; 1158 | term.line[i+n] = temp; 1159 | } 1160 | 1161 | selscroll(orig, -n); 1162 | } 1163 | 1164 | void 1165 | selscroll(int orig, int n) 1166 | { 1167 | if (sel.ob.x == -1) 1168 | return; 1169 | 1170 | if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 1171 | if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 1172 | selclear(); 1173 | return; 1174 | } 1175 | if (sel.type == SEL_RECTANGULAR) { 1176 | if (sel.ob.y < term.top) 1177 | sel.ob.y = term.top; 1178 | if (sel.oe.y > term.bot) 1179 | sel.oe.y = term.bot; 1180 | } else { 1181 | if (sel.ob.y < term.top) { 1182 | sel.ob.y = term.top; 1183 | sel.ob.x = 0; 1184 | } 1185 | if (sel.oe.y > term.bot) { 1186 | sel.oe.y = term.bot; 1187 | sel.oe.x = term.col; 1188 | } 1189 | } 1190 | selnormalize(); 1191 | } 1192 | } 1193 | 1194 | void 1195 | tnewline(int first_col) 1196 | { 1197 | int y = term.c.y; 1198 | 1199 | if (y == term.bot) { 1200 | tscrollup(term.top, 1, 1); 1201 | } else { 1202 | y++; 1203 | } 1204 | tmoveto(first_col ? 0 : term.c.x, y); 1205 | } 1206 | 1207 | void 1208 | csiparse(void) 1209 | { 1210 | char *p = csiescseq.buf, *np; 1211 | long int v; 1212 | 1213 | csiescseq.narg = 0; 1214 | if (*p == '?') { 1215 | csiescseq.priv = 1; 1216 | p++; 1217 | } 1218 | 1219 | csiescseq.buf[csiescseq.len] = '\0'; 1220 | while (p < csiescseq.buf+csiescseq.len) { 1221 | np = NULL; 1222 | v = strtol(p, &np, 10); 1223 | if (np == p) 1224 | v = 0; 1225 | if (v == LONG_MAX || v == LONG_MIN) 1226 | v = -1; 1227 | csiescseq.arg[csiescseq.narg++] = v; 1228 | p = np; 1229 | if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1230 | break; 1231 | p++; 1232 | } 1233 | csiescseq.mode[0] = *p++; 1234 | csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1235 | } 1236 | 1237 | /* for absolute user moves, when decom is set */ 1238 | void 1239 | tmoveato(int x, int y) 1240 | { 1241 | tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1242 | } 1243 | 1244 | void 1245 | tmoveto(int x, int y) 1246 | { 1247 | int miny, maxy; 1248 | 1249 | if (term.c.state & CURSOR_ORIGIN) { 1250 | miny = term.top; 1251 | maxy = term.bot; 1252 | } else { 1253 | miny = 0; 1254 | maxy = term.row - 1; 1255 | } 1256 | term.c.state &= ~CURSOR_WRAPNEXT; 1257 | term.c.x = LIMIT(x, 0, term.col-1); 1258 | term.c.y = LIMIT(y, miny, maxy); 1259 | } 1260 | 1261 | void 1262 | tsetchar(Rune u, Glyph *attr, int x, int y) 1263 | { 1264 | static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1265 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1266 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1267 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1268 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1269 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1270 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1271 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1272 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1273 | }; 1274 | 1275 | /* 1276 | * The table is proudly stolen from rxvt. 1277 | */ 1278 | if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1279 | BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1280 | utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1281 | 1282 | if (term.line[y][x].mode & ATTR_WIDE) { 1283 | if (x+1 < term.col) { 1284 | term.line[y][x+1].u = ' '; 1285 | term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1286 | } 1287 | } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1288 | term.line[y][x-1].u = ' '; 1289 | term.line[y][x-1].mode &= ~ATTR_WIDE; 1290 | } 1291 | 1292 | term.dirty[y] = 1; 1293 | term.line[y][x] = *attr; 1294 | term.line[y][x].u = u; 1295 | } 1296 | 1297 | void 1298 | tclearregion(int x1, int y1, int x2, int y2) 1299 | { 1300 | int x, y, temp; 1301 | Glyph *gp; 1302 | 1303 | if (x1 > x2) 1304 | temp = x1, x1 = x2, x2 = temp; 1305 | if (y1 > y2) 1306 | temp = y1, y1 = y2, y2 = temp; 1307 | 1308 | LIMIT(x1, 0, term.col-1); 1309 | LIMIT(x2, 0, term.col-1); 1310 | LIMIT(y1, 0, term.row-1); 1311 | LIMIT(y2, 0, term.row-1); 1312 | 1313 | for (y = y1; y <= y2; y++) { 1314 | term.dirty[y] = 1; 1315 | for (x = x1; x <= x2; x++) { 1316 | gp = &term.line[y][x]; 1317 | if (selected(x, y)) 1318 | selclear(); 1319 | gp->fg = term.c.attr.fg; 1320 | gp->bg = term.c.attr.bg; 1321 | gp->mode = 0; 1322 | gp->u = ' '; 1323 | } 1324 | } 1325 | } 1326 | 1327 | void 1328 | tdeletechar(int n) 1329 | { 1330 | int dst, src, size; 1331 | Glyph *line; 1332 | 1333 | LIMIT(n, 0, term.col - term.c.x); 1334 | 1335 | dst = term.c.x; 1336 | src = term.c.x + n; 1337 | size = term.col - src; 1338 | line = term.line[term.c.y]; 1339 | 1340 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1341 | tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1342 | } 1343 | 1344 | void 1345 | tinsertblank(int n) 1346 | { 1347 | int dst, src, size; 1348 | Glyph *line; 1349 | 1350 | LIMIT(n, 0, term.col - term.c.x); 1351 | 1352 | dst = term.c.x + n; 1353 | src = term.c.x; 1354 | size = term.col - dst; 1355 | line = term.line[term.c.y]; 1356 | 1357 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1358 | tclearregion(src, term.c.y, dst - 1, term.c.y); 1359 | } 1360 | 1361 | void 1362 | tinsertblankline(int n) 1363 | { 1364 | if (BETWEEN(term.c.y, term.top, term.bot)) 1365 | tscrolldown(term.c.y, n, 0); 1366 | } 1367 | 1368 | void 1369 | tdeleteline(int n) 1370 | { 1371 | if (BETWEEN(term.c.y, term.top, term.bot)) 1372 | tscrollup(term.c.y, n, 0); 1373 | } 1374 | 1375 | int32_t 1376 | tdefcolor(int *attr, int *npar, int l) 1377 | { 1378 | int32_t idx = -1; 1379 | uint r, g, b; 1380 | 1381 | switch (attr[*npar + 1]) { 1382 | case 2: /* direct color in RGB space */ 1383 | if (*npar + 4 >= l) { 1384 | fprintf(stderr, 1385 | "erresc(38): Incorrect number of parameters (%d)\n", 1386 | *npar); 1387 | break; 1388 | } 1389 | r = attr[*npar + 2]; 1390 | g = attr[*npar + 3]; 1391 | b = attr[*npar + 4]; 1392 | *npar += 4; 1393 | if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1394 | fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1395 | r, g, b); 1396 | else 1397 | idx = TRUECOLOR(r, g, b); 1398 | break; 1399 | case 5: /* indexed color */ 1400 | if (*npar + 2 >= l) { 1401 | fprintf(stderr, 1402 | "erresc(38): Incorrect number of parameters (%d)\n", 1403 | *npar); 1404 | break; 1405 | } 1406 | *npar += 2; 1407 | if (!BETWEEN(attr[*npar], 0, 255)) 1408 | fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1409 | else 1410 | idx = attr[*npar]; 1411 | break; 1412 | case 0: /* implemented defined (only foreground) */ 1413 | case 1: /* transparent */ 1414 | case 3: /* direct color in CMY space */ 1415 | case 4: /* direct color in CMYK space */ 1416 | default: 1417 | fprintf(stderr, 1418 | "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1419 | break; 1420 | } 1421 | 1422 | return idx; 1423 | } 1424 | 1425 | void 1426 | tsetattr(int *attr, int l) 1427 | { 1428 | int i; 1429 | int32_t idx; 1430 | 1431 | for (i = 0; i < l; i++) { 1432 | switch (attr[i]) { 1433 | case 0: 1434 | term.c.attr.mode &= ~( 1435 | ATTR_BOLD | 1436 | ATTR_FAINT | 1437 | ATTR_ITALIC | 1438 | ATTR_UNDERLINE | 1439 | ATTR_BLINK | 1440 | ATTR_REVERSE | 1441 | ATTR_INVISIBLE | 1442 | ATTR_STRUCK ); 1443 | term.c.attr.fg = defaultfg; 1444 | term.c.attr.bg = defaultbg; 1445 | break; 1446 | case 1: 1447 | term.c.attr.mode |= ATTR_BOLD; 1448 | break; 1449 | case 2: 1450 | term.c.attr.mode |= ATTR_FAINT; 1451 | break; 1452 | case 3: 1453 | term.c.attr.mode |= ATTR_ITALIC; 1454 | break; 1455 | case 4: 1456 | term.c.attr.mode |= ATTR_UNDERLINE; 1457 | break; 1458 | case 5: /* slow blink */ 1459 | /* FALLTHROUGH */ 1460 | case 6: /* rapid blink */ 1461 | term.c.attr.mode |= ATTR_BLINK; 1462 | break; 1463 | case 7: 1464 | term.c.attr.mode |= ATTR_REVERSE; 1465 | break; 1466 | case 8: 1467 | term.c.attr.mode |= ATTR_INVISIBLE; 1468 | break; 1469 | case 9: 1470 | term.c.attr.mode |= ATTR_STRUCK; 1471 | break; 1472 | case 22: 1473 | term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1474 | break; 1475 | case 23: 1476 | term.c.attr.mode &= ~ATTR_ITALIC; 1477 | break; 1478 | case 24: 1479 | term.c.attr.mode &= ~ATTR_UNDERLINE; 1480 | break; 1481 | case 25: 1482 | term.c.attr.mode &= ~ATTR_BLINK; 1483 | break; 1484 | case 27: 1485 | term.c.attr.mode &= ~ATTR_REVERSE; 1486 | break; 1487 | case 28: 1488 | term.c.attr.mode &= ~ATTR_INVISIBLE; 1489 | break; 1490 | case 29: 1491 | term.c.attr.mode &= ~ATTR_STRUCK; 1492 | break; 1493 | case 38: 1494 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1495 | term.c.attr.fg = idx; 1496 | break; 1497 | case 39: 1498 | term.c.attr.fg = defaultfg; 1499 | break; 1500 | case 48: 1501 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1502 | term.c.attr.bg = idx; 1503 | break; 1504 | case 49: 1505 | term.c.attr.bg = defaultbg; 1506 | break; 1507 | default: 1508 | if (BETWEEN(attr[i], 30, 37)) { 1509 | term.c.attr.fg = attr[i] - 30; 1510 | } else if (BETWEEN(attr[i], 40, 47)) { 1511 | term.c.attr.bg = attr[i] - 40; 1512 | } else if (BETWEEN(attr[i], 90, 97)) { 1513 | term.c.attr.fg = attr[i] - 90 + 8; 1514 | } else if (BETWEEN(attr[i], 100, 107)) { 1515 | term.c.attr.bg = attr[i] - 100 + 8; 1516 | } else { 1517 | fprintf(stderr, 1518 | "erresc(default): gfx attr %d unknown\n", 1519 | attr[i]); 1520 | csidump(); 1521 | } 1522 | break; 1523 | } 1524 | } 1525 | } 1526 | 1527 | void 1528 | tsetscroll(int t, int b) 1529 | { 1530 | int temp; 1531 | 1532 | LIMIT(t, 0, term.row-1); 1533 | LIMIT(b, 0, term.row-1); 1534 | if (t > b) { 1535 | temp = t; 1536 | t = b; 1537 | b = temp; 1538 | } 1539 | term.top = t; 1540 | term.bot = b; 1541 | } 1542 | 1543 | void 1544 | tsetmode(int priv, int set, int *args, int narg) 1545 | { 1546 | int alt, *lim; 1547 | 1548 | for (lim = args + narg; args < lim; ++args) { 1549 | if (priv) { 1550 | switch (*args) { 1551 | case 1: /* DECCKM -- Cursor key */ 1552 | xsetmode(set, MODE_APPCURSOR); 1553 | break; 1554 | case 5: /* DECSCNM -- Reverse video */ 1555 | xsetmode(set, MODE_REVERSE); 1556 | break; 1557 | case 6: /* DECOM -- Origin */ 1558 | MODBIT(term.c.state, set, CURSOR_ORIGIN); 1559 | tmoveato(0, 0); 1560 | break; 1561 | case 7: /* DECAWM -- Auto wrap */ 1562 | MODBIT(term.mode, set, MODE_WRAP); 1563 | break; 1564 | case 0: /* Error (IGNORED) */ 1565 | case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1566 | case 3: /* DECCOLM -- Column (IGNORED) */ 1567 | case 4: /* DECSCLM -- Scroll (IGNORED) */ 1568 | case 8: /* DECARM -- Auto repeat (IGNORED) */ 1569 | case 18: /* DECPFF -- Printer feed (IGNORED) */ 1570 | case 19: /* DECPEX -- Printer extent (IGNORED) */ 1571 | case 42: /* DECNRCM -- National characters (IGNORED) */ 1572 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1573 | break; 1574 | case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1575 | xsetmode(!set, MODE_HIDE); 1576 | break; 1577 | case 9: /* X10 mouse compatibility mode */ 1578 | xsetpointermotion(0); 1579 | xsetmode(0, MODE_MOUSE); 1580 | xsetmode(set, MODE_MOUSEX10); 1581 | break; 1582 | case 1000: /* 1000: report button press */ 1583 | xsetpointermotion(0); 1584 | xsetmode(0, MODE_MOUSE); 1585 | xsetmode(set, MODE_MOUSEBTN); 1586 | break; 1587 | case 1002: /* 1002: report motion on button press */ 1588 | xsetpointermotion(0); 1589 | xsetmode(0, MODE_MOUSE); 1590 | xsetmode(set, MODE_MOUSEMOTION); 1591 | break; 1592 | case 1003: /* 1003: enable all mouse motions */ 1593 | xsetpointermotion(set); 1594 | xsetmode(0, MODE_MOUSE); 1595 | xsetmode(set, MODE_MOUSEMANY); 1596 | break; 1597 | case 1004: /* 1004: send focus events to tty */ 1598 | xsetmode(set, MODE_FOCUS); 1599 | break; 1600 | case 1006: /* 1006: extended reporting mode */ 1601 | xsetmode(set, MODE_MOUSESGR); 1602 | break; 1603 | case 1034: 1604 | xsetmode(set, MODE_8BIT); 1605 | break; 1606 | case 1049: /* swap screen & set/restore cursor as xterm */ 1607 | if (!allowaltscreen) 1608 | break; 1609 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1610 | /* FALLTHROUGH */ 1611 | case 47: /* swap screen */ 1612 | case 1047: 1613 | if (!allowaltscreen) 1614 | break; 1615 | alt = IS_SET(MODE_ALTSCREEN); 1616 | if (alt) { 1617 | tclearregion(0, 0, term.col-1, 1618 | term.row-1); 1619 | } 1620 | if (set ^ alt) /* set is always 1 or 0 */ 1621 | tswapscreen(); 1622 | if (*args != 1049) 1623 | break; 1624 | /* FALLTHROUGH */ 1625 | case 1048: 1626 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1627 | break; 1628 | case 2004: /* 2004: bracketed paste mode */ 1629 | xsetmode(set, MODE_BRCKTPASTE); 1630 | break; 1631 | /* Not implemented mouse modes. See comments there. */ 1632 | case 1001: /* mouse highlight mode; can hang the 1633 | terminal by design when implemented. */ 1634 | case 1005: /* UTF-8 mouse mode; will confuse 1635 | applications not supporting UTF-8 1636 | and luit. */ 1637 | case 1015: /* urxvt mangled mouse mode; incompatible 1638 | and can be mistaken for other control 1639 | codes. */ 1640 | break; 1641 | default: 1642 | fprintf(stderr, 1643 | "erresc: unknown private set/reset mode %d\n", 1644 | *args); 1645 | break; 1646 | } 1647 | } else { 1648 | switch (*args) { 1649 | case 0: /* Error (IGNORED) */ 1650 | break; 1651 | case 2: 1652 | xsetmode(set, MODE_KBDLOCK); 1653 | break; 1654 | case 4: /* IRM -- Insertion-replacement */ 1655 | MODBIT(term.mode, set, MODE_INSERT); 1656 | break; 1657 | case 12: /* SRM -- Send/Receive */ 1658 | MODBIT(term.mode, !set, MODE_ECHO); 1659 | break; 1660 | case 20: /* LNM -- Linefeed/new line */ 1661 | MODBIT(term.mode, set, MODE_CRLF); 1662 | break; 1663 | default: 1664 | fprintf(stderr, 1665 | "erresc: unknown set/reset mode %d\n", 1666 | *args); 1667 | break; 1668 | } 1669 | } 1670 | } 1671 | } 1672 | 1673 | void 1674 | csihandle(void) 1675 | { 1676 | char buf[40]; 1677 | int len; 1678 | 1679 | switch (csiescseq.mode[0]) { 1680 | default: 1681 | unknown: 1682 | fprintf(stderr, "erresc: unknown csi "); 1683 | csidump(); 1684 | /* die(""); */ 1685 | break; 1686 | case '@': /* ICH -- Insert blank char */ 1687 | DEFAULT(csiescseq.arg[0], 1); 1688 | tinsertblank(csiescseq.arg[0]); 1689 | break; 1690 | case 'A': /* CUU -- Cursor Up */ 1691 | DEFAULT(csiescseq.arg[0], 1); 1692 | tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1693 | break; 1694 | case 'B': /* CUD -- Cursor Down */ 1695 | case 'e': /* VPR --Cursor Down */ 1696 | DEFAULT(csiescseq.arg[0], 1); 1697 | tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1698 | break; 1699 | case 'i': /* MC -- Media Copy */ 1700 | switch (csiescseq.arg[0]) { 1701 | case 0: 1702 | tdump(); 1703 | break; 1704 | case 1: 1705 | tdumpline(term.c.y); 1706 | break; 1707 | case 2: 1708 | tdumpsel(); 1709 | break; 1710 | case 4: 1711 | term.mode &= ~MODE_PRINT; 1712 | break; 1713 | case 5: 1714 | term.mode |= MODE_PRINT; 1715 | break; 1716 | } 1717 | break; 1718 | case 'c': /* DA -- Device Attributes */ 1719 | if (csiescseq.arg[0] == 0) 1720 | ttywrite(vtiden, strlen(vtiden), 0); 1721 | break; 1722 | case 'C': /* CUF -- Cursor Forward */ 1723 | case 'a': /* HPR -- Cursor Forward */ 1724 | DEFAULT(csiescseq.arg[0], 1); 1725 | tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1726 | break; 1727 | case 'D': /* CUB -- Cursor Backward */ 1728 | DEFAULT(csiescseq.arg[0], 1); 1729 | tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1730 | break; 1731 | case 'E': /* CNL -- Cursor Down and first col */ 1732 | DEFAULT(csiescseq.arg[0], 1); 1733 | tmoveto(0, term.c.y+csiescseq.arg[0]); 1734 | break; 1735 | case 'F': /* CPL -- Cursor Up and first col */ 1736 | DEFAULT(csiescseq.arg[0], 1); 1737 | tmoveto(0, term.c.y-csiescseq.arg[0]); 1738 | break; 1739 | case 'g': /* TBC -- Tabulation clear */ 1740 | switch (csiescseq.arg[0]) { 1741 | case 0: /* clear current tab stop */ 1742 | term.tabs[term.c.x] = 0; 1743 | break; 1744 | case 3: /* clear all the tabs */ 1745 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1746 | break; 1747 | default: 1748 | goto unknown; 1749 | } 1750 | break; 1751 | case 'G': /* CHA -- Move to */ 1752 | case '`': /* HPA */ 1753 | DEFAULT(csiescseq.arg[0], 1); 1754 | tmoveto(csiescseq.arg[0]-1, term.c.y); 1755 | break; 1756 | case 'H': /* CUP -- Move to */ 1757 | case 'f': /* HVP */ 1758 | DEFAULT(csiescseq.arg[0], 1); 1759 | DEFAULT(csiescseq.arg[1], 1); 1760 | tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1761 | break; 1762 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 1763 | DEFAULT(csiescseq.arg[0], 1); 1764 | tputtab(csiescseq.arg[0]); 1765 | break; 1766 | case 'J': /* ED -- Clear screen */ 1767 | switch (csiescseq.arg[0]) { 1768 | case 0: /* below */ 1769 | tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1770 | if (term.c.y < term.row-1) { 1771 | tclearregion(0, term.c.y+1, term.col-1, 1772 | term.row-1); 1773 | } 1774 | break; 1775 | case 1: /* above */ 1776 | if (term.c.y > 1) 1777 | tclearregion(0, 0, term.col-1, term.c.y-1); 1778 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1779 | break; 1780 | case 2: /* all */ 1781 | tclearregion(0, 0, term.col-1, term.row-1); 1782 | break; 1783 | default: 1784 | goto unknown; 1785 | } 1786 | break; 1787 | case 'K': /* EL -- Clear line */ 1788 | switch (csiescseq.arg[0]) { 1789 | case 0: /* right */ 1790 | tclearregion(term.c.x, term.c.y, term.col-1, 1791 | term.c.y); 1792 | break; 1793 | case 1: /* left */ 1794 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1795 | break; 1796 | case 2: /* all */ 1797 | tclearregion(0, term.c.y, term.col-1, term.c.y); 1798 | break; 1799 | } 1800 | break; 1801 | case 'S': /* SU -- Scroll line up */ 1802 | DEFAULT(csiescseq.arg[0], 1); 1803 | tscrollup(term.top, csiescseq.arg[0], 0); 1804 | break; 1805 | case 'T': /* SD -- Scroll line down */ 1806 | DEFAULT(csiescseq.arg[0], 1); 1807 | tscrolldown(term.top, csiescseq.arg[0], 0); 1808 | break; 1809 | case 'L': /* IL -- Insert blank lines */ 1810 | DEFAULT(csiescseq.arg[0], 1); 1811 | tinsertblankline(csiescseq.arg[0]); 1812 | break; 1813 | case 'l': /* RM -- Reset Mode */ 1814 | tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1815 | break; 1816 | case 'M': /* DL -- Delete lines */ 1817 | DEFAULT(csiescseq.arg[0], 1); 1818 | tdeleteline(csiescseq.arg[0]); 1819 | break; 1820 | case 'X': /* ECH -- Erase char */ 1821 | DEFAULT(csiescseq.arg[0], 1); 1822 | tclearregion(term.c.x, term.c.y, 1823 | term.c.x + csiescseq.arg[0] - 1, term.c.y); 1824 | break; 1825 | case 'P': /* DCH -- Delete char */ 1826 | DEFAULT(csiescseq.arg[0], 1); 1827 | tdeletechar(csiescseq.arg[0]); 1828 | break; 1829 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 1830 | DEFAULT(csiescseq.arg[0], 1); 1831 | tputtab(-csiescseq.arg[0]); 1832 | break; 1833 | case 'd': /* VPA -- Move to */ 1834 | DEFAULT(csiescseq.arg[0], 1); 1835 | tmoveato(term.c.x, csiescseq.arg[0]-1); 1836 | break; 1837 | case 'h': /* SM -- Set terminal mode */ 1838 | tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1839 | break; 1840 | case 'm': /* SGR -- Terminal attribute (color) */ 1841 | tsetattr(csiescseq.arg, csiescseq.narg); 1842 | break; 1843 | case 'n': /* DSR – Device Status Report (cursor position) */ 1844 | if (csiescseq.arg[0] == 6) { 1845 | len = snprintf(buf, sizeof(buf),"\033[%i;%iR", 1846 | term.c.y+1, term.c.x+1); 1847 | ttywrite(buf, len, 0); 1848 | } 1849 | break; 1850 | case 'r': /* DECSTBM -- Set Scrolling Region */ 1851 | if (csiescseq.priv) { 1852 | goto unknown; 1853 | } else { 1854 | DEFAULT(csiescseq.arg[0], 1); 1855 | DEFAULT(csiescseq.arg[1], term.row); 1856 | tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1857 | tmoveato(0, 0); 1858 | } 1859 | break; 1860 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1861 | tcursor(CURSOR_SAVE); 1862 | break; 1863 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1864 | tcursor(CURSOR_LOAD); 1865 | break; 1866 | case ' ': 1867 | switch (csiescseq.mode[1]) { 1868 | case 'q': /* DECSCUSR -- Set Cursor Style */ 1869 | if (xsetcursor(csiescseq.arg[0])) 1870 | goto unknown; 1871 | break; 1872 | default: 1873 | goto unknown; 1874 | } 1875 | break; 1876 | } 1877 | } 1878 | 1879 | void 1880 | csidump(void) 1881 | { 1882 | int i; 1883 | uint c; 1884 | 1885 | fprintf(stderr, "ESC["); 1886 | for (i = 0; i < csiescseq.len; i++) { 1887 | c = csiescseq.buf[i] & 0xff; 1888 | if (isprint(c)) { 1889 | putc(c, stderr); 1890 | } else if (c == '\n') { 1891 | fprintf(stderr, "(\\n)"); 1892 | } else if (c == '\r') { 1893 | fprintf(stderr, "(\\r)"); 1894 | } else if (c == 0x1b) { 1895 | fprintf(stderr, "(\\e)"); 1896 | } else { 1897 | fprintf(stderr, "(%02x)", c); 1898 | } 1899 | } 1900 | putc('\n', stderr); 1901 | } 1902 | 1903 | void 1904 | csireset(void) 1905 | { 1906 | memset(&csiescseq, 0, sizeof(csiescseq)); 1907 | } 1908 | 1909 | void 1910 | strhandle(void) 1911 | { 1912 | char *p = NULL, *dec; 1913 | int j, narg, par; 1914 | 1915 | term.esc &= ~(ESC_STR_END|ESC_STR); 1916 | strparse(); 1917 | par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1918 | 1919 | switch (strescseq.type) { 1920 | case ']': /* OSC -- Operating System Command */ 1921 | switch (par) { 1922 | case 0: 1923 | case 1: 1924 | case 2: 1925 | if (narg > 1) 1926 | xsettitle(strescseq.args[1]); 1927 | return; 1928 | case 52: 1929 | if (narg > 2) { 1930 | dec = base64dec(strescseq.args[2]); 1931 | if (dec) { 1932 | xsetsel(dec); 1933 | xclipcopy(); 1934 | } else { 1935 | fprintf(stderr, "erresc: invalid base64\n"); 1936 | } 1937 | } 1938 | return; 1939 | case 4: /* color set */ 1940 | if (narg < 3) 1941 | break; 1942 | p = strescseq.args[2]; 1943 | /* FALLTHROUGH */ 1944 | case 104: /* color reset, here p = NULL */ 1945 | j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1946 | if (xsetcolorname(j, p)) { 1947 | if (par == 104 && narg <= 1) 1948 | return; /* color reset without parameter */ 1949 | fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1950 | j, p ? p : "(null)"); 1951 | } else { 1952 | /* 1953 | * TODO if defaultbg color is changed, borders 1954 | * are dirty 1955 | */ 1956 | redraw(); 1957 | } 1958 | return; 1959 | } 1960 | break; 1961 | case 'k': /* old title set compatibility */ 1962 | xsettitle(strescseq.args[0]); 1963 | return; 1964 | case 'P': /* DCS -- Device Control String */ 1965 | term.mode |= ESC_DCS; 1966 | case '_': /* APC -- Application Program Command */ 1967 | case '^': /* PM -- Privacy Message */ 1968 | return; 1969 | } 1970 | 1971 | fprintf(stderr, "erresc: unknown str "); 1972 | strdump(); 1973 | } 1974 | 1975 | void 1976 | strparse(void) 1977 | { 1978 | int c; 1979 | char *p = strescseq.buf; 1980 | 1981 | strescseq.narg = 0; 1982 | strescseq.buf[strescseq.len] = '\0'; 1983 | 1984 | if (*p == '\0') 1985 | return; 1986 | 1987 | while (strescseq.narg < STR_ARG_SIZ) { 1988 | strescseq.args[strescseq.narg++] = p; 1989 | while ((c = *p) != ';' && c != '\0') 1990 | ++p; 1991 | if (c == '\0') 1992 | return; 1993 | *p++ = '\0'; 1994 | } 1995 | } 1996 | 1997 | void 1998 | strdump(void) 1999 | { 2000 | int i; 2001 | uint c; 2002 | 2003 | fprintf(stderr, "ESC%c", strescseq.type); 2004 | for (i = 0; i < strescseq.len; i++) { 2005 | c = strescseq.buf[i] & 0xff; 2006 | if (c == '\0') { 2007 | putc('\n', stderr); 2008 | return; 2009 | } else if (isprint(c)) { 2010 | putc(c, stderr); 2011 | } else if (c == '\n') { 2012 | fprintf(stderr, "(\\n)"); 2013 | } else if (c == '\r') { 2014 | fprintf(stderr, "(\\r)"); 2015 | } else if (c == 0x1b) { 2016 | fprintf(stderr, "(\\e)"); 2017 | } else { 2018 | fprintf(stderr, "(%02x)", c); 2019 | } 2020 | } 2021 | fprintf(stderr, "ESC\\\n"); 2022 | } 2023 | 2024 | void 2025 | strreset(void) 2026 | { 2027 | memset(&strescseq, 0, sizeof(strescseq)); 2028 | } 2029 | 2030 | void 2031 | sendbreak(const Arg *arg) 2032 | { 2033 | if (tcsendbreak(cmdfd, 0)) 2034 | perror("Error sending break"); 2035 | } 2036 | 2037 | void 2038 | tprinter(char *s, size_t len) 2039 | { 2040 | if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2041 | perror("Error writing to output file"); 2042 | close(iofd); 2043 | iofd = -1; 2044 | } 2045 | } 2046 | 2047 | void 2048 | externalpipe(const Arg *arg) 2049 | { 2050 | int to[2]; 2051 | char buf[UTF_SIZ]; 2052 | void (*oldsigpipe)(int); 2053 | Glyph *bp, *end; 2054 | int lastpos, n, newline; 2055 | 2056 | if (pipe(to) == -1) 2057 | return; 2058 | 2059 | switch (fork()) { 2060 | case -1: 2061 | close(to[0]); 2062 | close(to[1]); 2063 | return; 2064 | case 0: 2065 | dup2(to[0], STDIN_FILENO); 2066 | close(to[0]); 2067 | close(to[1]); 2068 | execvp(((char **)arg->v)[0], (char **)arg->v); 2069 | fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); 2070 | perror("failed"); 2071 | exit(0); 2072 | } 2073 | 2074 | close(to[0]); 2075 | /* ignore sigpipe for now, in case child exists early */ 2076 | oldsigpipe = signal(SIGPIPE, SIG_IGN); 2077 | newline = 0; 2078 | /* modify externalpipe patch to pipe history too */ 2079 | for (n = 0; n <= HISTSIZE + 2; n++) { 2080 | bp = TLINE_HIST(n); 2081 | lastpos = MIN(tlinehistlen(n) +1, term.col) - 1; 2082 | if (lastpos < 0) 2083 | break; 2084 | if (lastpos == 0) 2085 | continue; 2086 | end = &bp[lastpos + 1]; 2087 | for (; bp < end; ++bp) 2088 | if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) 2089 | break; 2090 | if ((newline = TLINE_HIST(n)[lastpos].mode & ATTR_WRAP)) 2091 | continue; 2092 | if (xwrite(to[1], "\n", 1) < 0) 2093 | break; 2094 | newline = 0; 2095 | } 2096 | if (newline) 2097 | (void)xwrite(to[1], "\n", 1); 2098 | close(to[1]); 2099 | /* restore */ 2100 | signal(SIGPIPE, oldsigpipe); 2101 | } 2102 | 2103 | void 2104 | iso14755(const Arg *arg) 2105 | { 2106 | FILE *p; 2107 | char *us, *e, codepoint[9], uc[UTF_SIZ]; 2108 | unsigned long utf32; 2109 | 2110 | if (!(p = popen(ISO14755CMD, "r"))) 2111 | return; 2112 | 2113 | us = fgets(codepoint, sizeof(codepoint), p); 2114 | pclose(p); 2115 | 2116 | if (!us || *us == '\0' || *us == '-' || strlen(us) > 7) 2117 | return; 2118 | if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX || 2119 | (*e != '\n' && *e != '\0')) 2120 | return; 2121 | 2122 | ttywrite(uc, utf8encode(utf32, uc), 1); 2123 | } 2124 | 2125 | void 2126 | toggleprinter(const Arg *arg) 2127 | { 2128 | term.mode ^= MODE_PRINT; 2129 | } 2130 | 2131 | void 2132 | printscreen(const Arg *arg) 2133 | { 2134 | tdump(); 2135 | } 2136 | 2137 | void 2138 | printsel(const Arg *arg) 2139 | { 2140 | tdumpsel(); 2141 | } 2142 | 2143 | void 2144 | tdumpsel(void) 2145 | { 2146 | char *ptr; 2147 | 2148 | if ((ptr = getsel())) { 2149 | tprinter(ptr, strlen(ptr)); 2150 | free(ptr); 2151 | } 2152 | } 2153 | 2154 | void 2155 | tdumpline(int n) 2156 | { 2157 | char buf[UTF_SIZ]; 2158 | Glyph *bp, *end; 2159 | 2160 | bp = &term.line[n][0]; 2161 | end = &bp[MIN(tlinelen(n), term.col) - 1]; 2162 | if (bp != end || bp->u != ' ') { 2163 | for ( ;bp <= end; ++bp) 2164 | tprinter(buf, utf8encode(bp->u, buf)); 2165 | } 2166 | tprinter("\n", 1); 2167 | } 2168 | 2169 | void 2170 | tdump(void) 2171 | { 2172 | int i; 2173 | 2174 | for (i = 0; i < term.row; ++i) 2175 | tdumpline(i); 2176 | } 2177 | 2178 | void 2179 | tputtab(int n) 2180 | { 2181 | uint x = term.c.x; 2182 | 2183 | if (n > 0) { 2184 | while (x < term.col && n--) 2185 | for (++x; x < term.col && !term.tabs[x]; ++x) 2186 | /* nothing */ ; 2187 | } else if (n < 0) { 2188 | while (x > 0 && n++) 2189 | for (--x; x > 0 && !term.tabs[x]; --x) 2190 | /* nothing */ ; 2191 | } 2192 | term.c.x = LIMIT(x, 0, term.col-1); 2193 | } 2194 | 2195 | void 2196 | tdefutf8(char ascii) 2197 | { 2198 | if (ascii == 'G') 2199 | term.mode |= MODE_UTF8; 2200 | else if (ascii == '@') 2201 | term.mode &= ~MODE_UTF8; 2202 | } 2203 | 2204 | void 2205 | tdeftran(char ascii) 2206 | { 2207 | static char cs[] = "0B"; 2208 | static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2209 | char *p; 2210 | 2211 | if ((p = strchr(cs, ascii)) == NULL) { 2212 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2213 | } else { 2214 | term.trantbl[term.icharset] = vcs[p - cs]; 2215 | } 2216 | } 2217 | 2218 | void 2219 | tdectest(char c) 2220 | { 2221 | int x, y; 2222 | 2223 | if (c == '8') { /* DEC screen alignment test. */ 2224 | for (x = 0; x < term.col; ++x) { 2225 | for (y = 0; y < term.row; ++y) 2226 | tsetchar('E', &term.c.attr, x, y); 2227 | } 2228 | } 2229 | } 2230 | 2231 | void 2232 | tstrsequence(uchar c) 2233 | { 2234 | strreset(); 2235 | 2236 | switch (c) { 2237 | case 0x90: /* DCS -- Device Control String */ 2238 | c = 'P'; 2239 | term.esc |= ESC_DCS; 2240 | break; 2241 | case 0x9f: /* APC -- Application Program Command */ 2242 | c = '_'; 2243 | break; 2244 | case 0x9e: /* PM -- Privacy Message */ 2245 | c = '^'; 2246 | break; 2247 | case 0x9d: /* OSC -- Operating System Command */ 2248 | c = ']'; 2249 | break; 2250 | } 2251 | strescseq.type = c; 2252 | term.esc |= ESC_STR; 2253 | } 2254 | 2255 | void 2256 | tcontrolcode(uchar ascii) 2257 | { 2258 | switch (ascii) { 2259 | case '\t': /* HT */ 2260 | tputtab(1); 2261 | return; 2262 | case '\b': /* BS */ 2263 | tmoveto(term.c.x-1, term.c.y); 2264 | return; 2265 | case '\r': /* CR */ 2266 | tmoveto(0, term.c.y); 2267 | return; 2268 | case '\f': /* LF */ 2269 | case '\v': /* VT */ 2270 | case '\n': /* LF */ 2271 | /* go to first col if the mode is set */ 2272 | tnewline(IS_SET(MODE_CRLF)); 2273 | return; 2274 | case '\a': /* BEL */ 2275 | if (term.esc & ESC_STR_END) { 2276 | /* backwards compatibility to xterm */ 2277 | strhandle(); 2278 | } else { 2279 | xbell(); 2280 | } 2281 | break; 2282 | case '\033': /* ESC */ 2283 | csireset(); 2284 | term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2285 | term.esc |= ESC_START; 2286 | return; 2287 | case '\016': /* SO (LS1 -- Locking shift 1) */ 2288 | case '\017': /* SI (LS0 -- Locking shift 0) */ 2289 | term.charset = 1 - (ascii - '\016'); 2290 | return; 2291 | case '\032': /* SUB */ 2292 | tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2293 | case '\030': /* CAN */ 2294 | csireset(); 2295 | break; 2296 | case '\005': /* ENQ (IGNORED) */ 2297 | case '\000': /* NUL (IGNORED) */ 2298 | case '\021': /* XON (IGNORED) */ 2299 | case '\023': /* XOFF (IGNORED) */ 2300 | case 0177: /* DEL (IGNORED) */ 2301 | return; 2302 | case 0x80: /* TODO: PAD */ 2303 | case 0x81: /* TODO: HOP */ 2304 | case 0x82: /* TODO: BPH */ 2305 | case 0x83: /* TODO: NBH */ 2306 | case 0x84: /* TODO: IND */ 2307 | break; 2308 | case 0x85: /* NEL -- Next line */ 2309 | tnewline(1); /* always go to first col */ 2310 | break; 2311 | case 0x86: /* TODO: SSA */ 2312 | case 0x87: /* TODO: ESA */ 2313 | break; 2314 | case 0x88: /* HTS -- Horizontal tab stop */ 2315 | term.tabs[term.c.x] = 1; 2316 | break; 2317 | case 0x89: /* TODO: HTJ */ 2318 | case 0x8a: /* TODO: VTS */ 2319 | case 0x8b: /* TODO: PLD */ 2320 | case 0x8c: /* TODO: PLU */ 2321 | case 0x8d: /* TODO: RI */ 2322 | case 0x8e: /* TODO: SS2 */ 2323 | case 0x8f: /* TODO: SS3 */ 2324 | case 0x91: /* TODO: PU1 */ 2325 | case 0x92: /* TODO: PU2 */ 2326 | case 0x93: /* TODO: STS */ 2327 | case 0x94: /* TODO: CCH */ 2328 | case 0x95: /* TODO: MW */ 2329 | case 0x96: /* TODO: SPA */ 2330 | case 0x97: /* TODO: EPA */ 2331 | case 0x98: /* TODO: SOS */ 2332 | case 0x99: /* TODO: SGCI */ 2333 | break; 2334 | case 0x9a: /* DECID -- Identify Terminal */ 2335 | ttywrite(vtiden, strlen(vtiden), 0); 2336 | break; 2337 | case 0x9b: /* TODO: CSI */ 2338 | case 0x9c: /* TODO: ST */ 2339 | break; 2340 | case 0x90: /* DCS -- Device Control String */ 2341 | case 0x9d: /* OSC -- Operating System Command */ 2342 | case 0x9e: /* PM -- Privacy Message */ 2343 | case 0x9f: /* APC -- Application Program Command */ 2344 | tstrsequence(ascii); 2345 | return; 2346 | } 2347 | /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2348 | term.esc &= ~(ESC_STR_END|ESC_STR); 2349 | } 2350 | 2351 | /* 2352 | * returns 1 when the sequence is finished and it hasn't to read 2353 | * more characters for this sequence, otherwise 0 2354 | */ 2355 | int 2356 | eschandle(uchar ascii) 2357 | { 2358 | switch (ascii) { 2359 | case '[': 2360 | term.esc |= ESC_CSI; 2361 | return 0; 2362 | case '#': 2363 | term.esc |= ESC_TEST; 2364 | return 0; 2365 | case '%': 2366 | term.esc |= ESC_UTF8; 2367 | return 0; 2368 | case 'P': /* DCS -- Device Control String */ 2369 | case '_': /* APC -- Application Program Command */ 2370 | case '^': /* PM -- Privacy Message */ 2371 | case ']': /* OSC -- Operating System Command */ 2372 | case 'k': /* old title set compatibility */ 2373 | tstrsequence(ascii); 2374 | return 0; 2375 | case 'n': /* LS2 -- Locking shift 2 */ 2376 | case 'o': /* LS3 -- Locking shift 3 */ 2377 | term.charset = 2 + (ascii - 'n'); 2378 | break; 2379 | case '(': /* GZD4 -- set primary charset G0 */ 2380 | case ')': /* G1D4 -- set secondary charset G1 */ 2381 | case '*': /* G2D4 -- set tertiary charset G2 */ 2382 | case '+': /* G3D4 -- set quaternary charset G3 */ 2383 | term.icharset = ascii - '('; 2384 | term.esc |= ESC_ALTCHARSET; 2385 | return 0; 2386 | case 'D': /* IND -- Linefeed */ 2387 | if (term.c.y == term.bot) { 2388 | tscrollup(term.top, 1, 1); 2389 | } else { 2390 | tmoveto(term.c.x, term.c.y+1); 2391 | } 2392 | break; 2393 | case 'E': /* NEL -- Next line */ 2394 | tnewline(1); /* always go to first col */ 2395 | break; 2396 | case 'H': /* HTS -- Horizontal tab stop */ 2397 | term.tabs[term.c.x] = 1; 2398 | break; 2399 | case 'M': /* RI -- Reverse index */ 2400 | if (term.c.y == term.top) { 2401 | tscrolldown(term.top, 1, 1); 2402 | } else { 2403 | tmoveto(term.c.x, term.c.y-1); 2404 | } 2405 | break; 2406 | case 'Z': /* DECID -- Identify Terminal */ 2407 | ttywrite(vtiden, strlen(vtiden), 0); 2408 | break; 2409 | case 'c': /* RIS -- Reset to initial state */ 2410 | treset(); 2411 | resettitle(); 2412 | xloadcols(); 2413 | break; 2414 | case '=': /* DECPAM -- Application keypad */ 2415 | xsetmode(1, MODE_APPKEYPAD); 2416 | break; 2417 | case '>': /* DECPNM -- Normal keypad */ 2418 | xsetmode(0, MODE_APPKEYPAD); 2419 | break; 2420 | case '7': /* DECSC -- Save Cursor */ 2421 | tcursor(CURSOR_SAVE); 2422 | break; 2423 | case '8': /* DECRC -- Restore Cursor */ 2424 | tcursor(CURSOR_LOAD); 2425 | break; 2426 | case '\\': /* ST -- String Terminator */ 2427 | if (term.esc & ESC_STR_END) 2428 | strhandle(); 2429 | break; 2430 | default: 2431 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2432 | (uchar) ascii, isprint(ascii)? ascii:'.'); 2433 | break; 2434 | } 2435 | return 1; 2436 | } 2437 | 2438 | void 2439 | tputc(Rune u) 2440 | { 2441 | char c[UTF_SIZ]; 2442 | int control; 2443 | int width, len; 2444 | Glyph *gp; 2445 | 2446 | control = ISCONTROL(u); 2447 | if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 2448 | c[0] = u; 2449 | width = len = 1; 2450 | } else { 2451 | len = utf8encode(u, c); 2452 | if (!control && (width = wcwidth(u)) == -1) { 2453 | memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ 2454 | width = 1; 2455 | } 2456 | } 2457 | 2458 | if (IS_SET(MODE_PRINT)) 2459 | tprinter(c, len); 2460 | 2461 | /* 2462 | * STR sequence must be checked before anything else 2463 | * because it uses all following characters until it 2464 | * receives a ESC, a SUB, a ST or any other C1 control 2465 | * character. 2466 | */ 2467 | if (term.esc & ESC_STR) { 2468 | if (u == '\a' || u == 030 || u == 032 || u == 033 || 2469 | ISCONTROLC1(u)) { 2470 | term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); 2471 | if (IS_SET(MODE_SIXEL)) { 2472 | /* TODO: render sixel */; 2473 | term.mode &= ~MODE_SIXEL; 2474 | return; 2475 | } 2476 | term.esc |= ESC_STR_END; 2477 | goto check_control_code; 2478 | } 2479 | 2480 | if (IS_SET(MODE_SIXEL)) { 2481 | /* TODO: implement sixel mode */ 2482 | return; 2483 | } 2484 | if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') 2485 | term.mode |= MODE_SIXEL; 2486 | 2487 | if (strescseq.len+len >= sizeof(strescseq.buf)-1) { 2488 | /* 2489 | * Here is a bug in terminals. If the user never sends 2490 | * some code to stop the str or esc command, then st 2491 | * will stop responding. But this is better than 2492 | * silently failing with unknown characters. At least 2493 | * then users will report back. 2494 | * 2495 | * In the case users ever get fixed, here is the code: 2496 | */ 2497 | /* 2498 | * term.esc = 0; 2499 | * strhandle(); 2500 | */ 2501 | return; 2502 | } 2503 | 2504 | memmove(&strescseq.buf[strescseq.len], c, len); 2505 | strescseq.len += len; 2506 | return; 2507 | } 2508 | 2509 | check_control_code: 2510 | /* 2511 | * Actions of control codes must be performed as soon they arrive 2512 | * because they can be embedded inside a control sequence, and 2513 | * they must not cause conflicts with sequences. 2514 | */ 2515 | if (control) { 2516 | tcontrolcode(u); 2517 | /* 2518 | * control codes are not shown ever 2519 | */ 2520 | return; 2521 | } else if (term.esc & ESC_START) { 2522 | if (term.esc & ESC_CSI) { 2523 | csiescseq.buf[csiescseq.len++] = u; 2524 | if (BETWEEN(u, 0x40, 0x7E) 2525 | || csiescseq.len >= \ 2526 | sizeof(csiescseq.buf)-1) { 2527 | term.esc = 0; 2528 | csiparse(); 2529 | csihandle(); 2530 | } 2531 | return; 2532 | } else if (term.esc & ESC_UTF8) { 2533 | tdefutf8(u); 2534 | } else if (term.esc & ESC_ALTCHARSET) { 2535 | tdeftran(u); 2536 | } else if (term.esc & ESC_TEST) { 2537 | tdectest(u); 2538 | } else { 2539 | if (!eschandle(u)) 2540 | return; 2541 | /* sequence already finished */ 2542 | } 2543 | term.esc = 0; 2544 | /* 2545 | * All characters which form part of a sequence are not 2546 | * printed 2547 | */ 2548 | return; 2549 | } 2550 | if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) 2551 | selclear(); 2552 | 2553 | gp = &term.line[term.c.y][term.c.x]; 2554 | if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2555 | gp->mode |= ATTR_WRAP; 2556 | tnewline(1); 2557 | gp = &term.line[term.c.y][term.c.x]; 2558 | } 2559 | 2560 | if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2561 | memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2562 | 2563 | if (term.c.x+width > term.col) { 2564 | tnewline(1); 2565 | gp = &term.line[term.c.y][term.c.x]; 2566 | } 2567 | 2568 | tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2569 | 2570 | if (width == 2) { 2571 | gp->mode |= ATTR_WIDE; 2572 | if (term.c.x+1 < term.col) { 2573 | gp[1].u = '\0'; 2574 | gp[1].mode = ATTR_WDUMMY; 2575 | } 2576 | } 2577 | if (term.c.x+width < term.col) { 2578 | tmoveto(term.c.x+width, term.c.y); 2579 | } else { 2580 | term.c.state |= CURSOR_WRAPNEXT; 2581 | } 2582 | } 2583 | 2584 | int 2585 | twrite(const char *buf, int buflen, int show_ctrl) 2586 | { 2587 | int charsize; 2588 | Rune u; 2589 | int n; 2590 | 2591 | for (n = 0; n < buflen; n += charsize) { 2592 | if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 2593 | /* process a complete utf8 char */ 2594 | charsize = utf8decode(buf + n, &u, buflen - n); 2595 | if (charsize == 0) 2596 | break; 2597 | } else { 2598 | u = buf[n] & 0xFF; 2599 | charsize = 1; 2600 | } 2601 | if (show_ctrl && ISCONTROL(u)) { 2602 | if (u & 0x80) { 2603 | u &= 0x7f; 2604 | tputc('^'); 2605 | tputc('['); 2606 | } else if (u != '\n' && u != '\r' && u != '\t') { 2607 | u ^= 0x40; 2608 | tputc('^'); 2609 | } 2610 | } 2611 | tputc(u); 2612 | } 2613 | return n; 2614 | } 2615 | 2616 | void 2617 | tresize(int col, int row) 2618 | { 2619 | int i, j; 2620 | int minrow = MIN(row, term.row); 2621 | int mincol = MIN(col, term.col); 2622 | int *bp; 2623 | TCursor c; 2624 | 2625 | if (col < 1 || row < 1) { 2626 | fprintf(stderr, 2627 | "tresize: error resizing to %dx%d\n", col, row); 2628 | return; 2629 | } 2630 | 2631 | /* 2632 | * slide screen to keep cursor where we expect it - 2633 | * tscrollup would work here, but we can optimize to 2634 | * memmove because we're freeing the earlier lines 2635 | */ 2636 | for (i = 0; i <= term.c.y - row; i++) { 2637 | free(term.line[i]); 2638 | free(term.alt[i]); 2639 | } 2640 | /* ensure that both src and dst are not NULL */ 2641 | if (i > 0) { 2642 | memmove(term.line, term.line + i, row * sizeof(Line)); 2643 | memmove(term.alt, term.alt + i, row * sizeof(Line)); 2644 | } 2645 | for (i += row; i < term.row; i++) { 2646 | free(term.line[i]); 2647 | free(term.alt[i]); 2648 | } 2649 | 2650 | /* resize to new height */ 2651 | term.line = xrealloc(term.line, row * sizeof(Line)); 2652 | term.alt = xrealloc(term.alt, row * sizeof(Line)); 2653 | term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2654 | term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2655 | 2656 | for (i = 0; i < HISTSIZE; i++) { 2657 | term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2658 | for (j = mincol; j < col; j++) { 2659 | term.hist[i][j] = term.c.attr; 2660 | term.hist[i][j].u = ' '; 2661 | } 2662 | } 2663 | 2664 | /* resize each row to new width, zero-pad if needed */ 2665 | for (i = 0; i < minrow; i++) { 2666 | term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2667 | term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2668 | } 2669 | 2670 | /* allocate any new rows */ 2671 | for (/* i = minrow */; i < row; i++) { 2672 | term.line[i] = xmalloc(col * sizeof(Glyph)); 2673 | term.alt[i] = xmalloc(col * sizeof(Glyph)); 2674 | } 2675 | if (col > term.col) { 2676 | bp = term.tabs + term.col; 2677 | 2678 | memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2679 | while (--bp > term.tabs && !*bp) 2680 | /* nothing */ ; 2681 | for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2682 | *bp = 1; 2683 | } 2684 | /* update terminal size */ 2685 | term.col = col; 2686 | term.row = row; 2687 | /* reset scrolling region */ 2688 | tsetscroll(0, row-1); 2689 | /* make use of the LIMIT in tmoveto */ 2690 | tmoveto(term.c.x, term.c.y); 2691 | /* Clearing both screens (it makes dirty all lines) */ 2692 | c = term.c; 2693 | for (i = 0; i < 2; i++) { 2694 | if (mincol < col && 0 < minrow) { 2695 | tclearregion(mincol, 0, col - 1, minrow - 1); 2696 | } 2697 | if (0 < col && minrow < row) { 2698 | tclearregion(0, minrow, col - 1, row - 1); 2699 | } 2700 | tswapscreen(); 2701 | tcursor(CURSOR_LOAD); 2702 | } 2703 | term.c = c; 2704 | } 2705 | 2706 | void 2707 | resettitle(void) 2708 | { 2709 | xsettitle(NULL); 2710 | } 2711 | 2712 | void 2713 | drawregion(int x1, int y1, int x2, int y2) 2714 | { 2715 | int y; 2716 | for (y = y1; y < y2; y++) { 2717 | if (!term.dirty[y]) 2718 | continue; 2719 | 2720 | term.dirty[y] = 0; 2721 | xdrawline(TLINE(y), x1, y, x2); 2722 | } 2723 | } 2724 | 2725 | void 2726 | draw(void) 2727 | { 2728 | int cx = term.c.x; 2729 | 2730 | if (!xstartdraw()) 2731 | return; 2732 | 2733 | /* adjust cursor position */ 2734 | LIMIT(term.ocx, 0, term.col-1); 2735 | LIMIT(term.ocy, 0, term.row-1); 2736 | if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2737 | term.ocx--; 2738 | if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2739 | cx--; 2740 | 2741 | drawregion(0, 0, term.col, term.row); 2742 | if (term.scr == 0) 2743 | xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2744 | term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2745 | term.ocx = cx, term.ocy = term.c.y; 2746 | xfinishdraw(); 2747 | xximspot(term.ocx, term.ocy); 2748 | } 2749 | 2750 | void 2751 | redraw(void) 2752 | { 2753 | tfulldirt(); 2754 | draw(); 2755 | } 2756 | -------------------------------------------------------------------------------- /st.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | #include 4 | #include 5 | 6 | /* macros */ 7 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 8 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 9 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 10 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 11 | #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 12 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 13 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 14 | #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 15 | (a).bg != (b).bg) 16 | #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 17 | (t1.tv_nsec-t2.tv_nsec)/1E6) 18 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 19 | 20 | #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 21 | #define IS_TRUECOL(x) (1 << 24 & (x)) 22 | 23 | enum glyph_attribute { 24 | ATTR_NULL = 0, 25 | ATTR_BOLD = 1 << 0, 26 | ATTR_FAINT = 1 << 1, 27 | ATTR_ITALIC = 1 << 2, 28 | ATTR_UNDERLINE = 1 << 3, 29 | ATTR_BLINK = 1 << 4, 30 | ATTR_REVERSE = 1 << 5, 31 | ATTR_INVISIBLE = 1 << 6, 32 | ATTR_STRUCK = 1 << 7, 33 | ATTR_WRAP = 1 << 8, 34 | ATTR_WIDE = 1 << 9, 35 | ATTR_WDUMMY = 1 << 10, 36 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 37 | }; 38 | 39 | enum selection_mode { 40 | SEL_IDLE = 0, 41 | SEL_EMPTY = 1, 42 | SEL_READY = 2 43 | }; 44 | 45 | enum selection_type { 46 | SEL_REGULAR = 1, 47 | SEL_RECTANGULAR = 2 48 | }; 49 | 50 | enum selection_snap { 51 | SNAP_WORD = 1, 52 | SNAP_LINE = 2 53 | }; 54 | 55 | typedef unsigned char uchar; 56 | typedef unsigned int uint; 57 | typedef unsigned long ulong; 58 | typedef unsigned short ushort; 59 | 60 | typedef uint_least32_t Rune; 61 | 62 | #define Glyph Glyph_ 63 | typedef struct { 64 | Rune u; /* character code */ 65 | ushort mode; /* attribute flags */ 66 | uint32_t fg; /* foreground */ 67 | uint32_t bg; /* background */ 68 | } Glyph; 69 | 70 | typedef Glyph *Line; 71 | 72 | typedef union { 73 | int i; 74 | uint ui; 75 | float f; 76 | const void *v; 77 | } Arg; 78 | 79 | typedef struct { 80 | uint b; 81 | uint mask; 82 | void (*func)(const Arg *); 83 | const Arg arg; 84 | } MouseKey; 85 | 86 | void die(const char *, ...); 87 | void redraw(void); 88 | void draw(void); 89 | 90 | void externalpipe(const Arg *); 91 | void iso14755(const Arg *); 92 | void kscrolldown(const Arg *); 93 | void kscrollup(const Arg *); 94 | void printscreen(const Arg *); 95 | void printsel(const Arg *); 96 | void sendbreak(const Arg *); 97 | void toggleprinter(const Arg *); 98 | 99 | int tattrset(int); 100 | void tnew(int, int); 101 | void tresize(int, int); 102 | void tsetdirtattr(int); 103 | void ttyhangup(void); 104 | int ttynew(char *, char *, char *, char **); 105 | size_t ttyread(void); 106 | void ttyresize(int, int); 107 | void ttywrite(const char *, size_t, int); 108 | 109 | void resettitle(void); 110 | 111 | void selclear(void); 112 | void selinit(void); 113 | void selstart(int, int, int); 114 | void selextend(int, int, int, int); 115 | int selected(int, int); 116 | char *getsel(void); 117 | 118 | size_t utf8encode(Rune, char *); 119 | 120 | void *xmalloc(size_t); 121 | void *xrealloc(void *, size_t); 122 | char *xstrdup(char *); 123 | 124 | /* config.h globals */ 125 | extern char *utmp; 126 | extern char *stty_args; 127 | extern char *vtiden; 128 | extern wchar_t *worddelimiters; 129 | extern int allowaltscreen; 130 | extern char *termname; 131 | extern unsigned int tabspaces; 132 | extern unsigned int defaultfg; 133 | extern unsigned int defaultbg; 134 | extern float alpha; 135 | extern MouseKey mkeys[]; 136 | -------------------------------------------------------------------------------- /st.info: -------------------------------------------------------------------------------- 1 | st| simpleterm, 2 | acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 3 | am, 4 | bce, 5 | bel=^G, 6 | blink=\E[5m, 7 | bold=\E[1m, 8 | cbt=\E[Z, 9 | cvvis=\E[?25h, 10 | civis=\E[?25l, 11 | clear=\E[H\E[2J, 12 | cnorm=\E[?12l\E[?25h, 13 | colors#8, 14 | cols#80, 15 | cr=^M, 16 | csr=\E[%i%p1%d;%p2%dr, 17 | cub=\E[%p1%dD, 18 | cub1=^H, 19 | cud1=^J, 20 | cud=\E[%p1%dB, 21 | cuf1=\E[C, 22 | cuf=\E[%p1%dC, 23 | cup=\E[%i%p1%d;%p2%dH, 24 | cuu1=\E[A, 25 | cuu=\E[%p1%dA, 26 | dch=\E[%p1%dP, 27 | dch1=\E[P, 28 | dim=\E[2m, 29 | dl=\E[%p1%dM, 30 | dl1=\E[M, 31 | ech=\E[%p1%dX, 32 | ed=\E[J, 33 | el=\E[K, 34 | el1=\E[1K, 35 | enacs=\E)0, 36 | flash=\E[?5h$<80/>\E[?5l, 37 | fsl=^G, 38 | home=\E[H, 39 | hpa=\E[%i%p1%dG, 40 | hs, 41 | ht=^I, 42 | hts=\EH, 43 | ich=\E[%p1%d@, 44 | il1=\E[L, 45 | il=\E[%p1%dL, 46 | ind=^J, 47 | indn=\E[%p1%dS, 48 | invis=\E[8m, 49 | is2=\E[4l\E>\E[?1034l, 50 | it#8, 51 | kel=\E[1;2F, 52 | ked=\E[1;5F, 53 | ka1=\E[1~, 54 | ka3=\E[5~, 55 | kc1=\E[4~, 56 | kc3=\E[6~, 57 | kbs=\177, 58 | kcbt=\E[Z, 59 | kb2=\EOu, 60 | kcub1=\EOD, 61 | kcud1=\EOB, 62 | kcuf1=\EOC, 63 | kcuu1=\EOA, 64 | kDC=\E[3;2~, 65 | kent=\EOM, 66 | kEND=\E[1;2F, 67 | kIC=\E[2;2~, 68 | kNXT=\E[6;2~, 69 | kPRV=\E[5;2~, 70 | kHOM=\E[1;2H, 71 | kLFT=\E[1;2D, 72 | kRIT=\E[1;2C, 73 | kind=\E[1;2B, 74 | kri=\E[1;2A, 75 | kclr=\E[3;5~, 76 | kdl1=\E[3;2~, 77 | kdch1=\E[3~, 78 | kich1=\E[2~, 79 | kend=\E[4~, 80 | kf1=\EOP, 81 | kf2=\EOQ, 82 | kf3=\EOR, 83 | kf4=\EOS, 84 | kf5=\E[15~, 85 | kf6=\E[17~, 86 | kf7=\E[18~, 87 | kf8=\E[19~, 88 | kf9=\E[20~, 89 | kf10=\E[21~, 90 | kf11=\E[23~, 91 | kf12=\E[24~, 92 | kf13=\E[1;2P, 93 | kf14=\E[1;2Q, 94 | kf15=\E[1;2R, 95 | kf16=\E[1;2S, 96 | kf17=\E[15;2~, 97 | kf18=\E[17;2~, 98 | kf19=\E[18;2~, 99 | kf20=\E[19;2~, 100 | kf21=\E[20;2~, 101 | kf22=\E[21;2~, 102 | kf23=\E[23;2~, 103 | kf24=\E[24;2~, 104 | kf25=\E[1;5P, 105 | kf26=\E[1;5Q, 106 | kf27=\E[1;5R, 107 | kf28=\E[1;5S, 108 | kf29=\E[15;5~, 109 | kf30=\E[17;5~, 110 | kf31=\E[18;5~, 111 | kf32=\E[19;5~, 112 | kf33=\E[20;5~, 113 | kf34=\E[21;5~, 114 | kf35=\E[23;5~, 115 | kf36=\E[24;5~, 116 | kf37=\E[1;6P, 117 | kf38=\E[1;6Q, 118 | kf39=\E[1;6R, 119 | kf40=\E[1;6S, 120 | kf41=\E[15;6~, 121 | kf42=\E[17;6~, 122 | kf43=\E[18;6~, 123 | kf44=\E[19;6~, 124 | kf45=\E[20;6~, 125 | kf46=\E[21;6~, 126 | kf47=\E[23;6~, 127 | kf48=\E[24;6~, 128 | kf49=\E[1;3P, 129 | kf50=\E[1;3Q, 130 | kf51=\E[1;3R, 131 | kf52=\E[1;3S, 132 | kf53=\E[15;3~, 133 | kf54=\E[17;3~, 134 | kf55=\E[18;3~, 135 | kf56=\E[19;3~, 136 | kf57=\E[20;3~, 137 | kf58=\E[21;3~, 138 | kf59=\E[23;3~, 139 | kf60=\E[24;3~, 140 | kf61=\E[1;4P, 141 | kf62=\E[1;4Q, 142 | kf63=\E[1;4R, 143 | khome=\E[1~, 144 | kil1=\E[2;5~, 145 | krmir=\E[2;2~, 146 | knp=\E[6~, 147 | kmous=\E[M, 148 | kpp=\E[5~, 149 | lines#24, 150 | mir, 151 | msgr, 152 | npc, 153 | op=\E[39;49m, 154 | pairs#64, 155 | mc0=\E[i, 156 | mc4=\E[4i, 157 | mc5=\E[5i, 158 | rc=\E8, 159 | rev=\E[7m, 160 | ri=\EM, 161 | ritm=\E[23m, 162 | rmacs=\E(B, 163 | rmcup=\E[?1049l, 164 | rmir=\E[4l, 165 | rmkx=\E[?1l\E>, 166 | rmso=\E[27m, 167 | rmul=\E[24m, 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 | smso=\E[7m, 183 | smul=\E[4m, 184 | tbc=\E[3g, 185 | tsl=\E]0;, 186 | xenl, 187 | vpa=\E[%i%p1%dd, 188 | # XTerm extensions 189 | rmxx=\E[29m, 190 | smxx=\E[9m, 191 | # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) 192 | Se, 193 | Ss, 194 | Tc, 195 | Ms=\E]52;%p1%s;%p2%s\007, 196 | 197 | st-256color| simpleterm with 256 colors, 198 | use=st, 199 | ccc, 200 | colors#256, 201 | oc=\E]104\007, 202 | pairs#32767, 203 | # Nicked from xterm-256color 204 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 205 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 206 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 207 | 208 | st-meta| simpleterm with meta key, 209 | use=st, 210 | km, 211 | rmm=\E[?1034l, 212 | smm=\E[?1034h, 213 | rs2=\E[4l\E>\E[?1034h, 214 | is2=\E[4l\E>\E[?1034h, 215 | 216 | st-meta-256color| simpleterm with meta key and 256 colors, 217 | use=st-256color, 218 | km, 219 | rmm=\E[?1034l, 220 | smm=\E[?1034h, 221 | rs2=\E[4l\E>\E[?1034h, 222 | is2=\E[4l\E>\E[?1034h, 223 | -------------------------------------------------------------------------------- /st.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MentalOutlaw/st/ac9aaa89114e801335b836c263cbf43fa16393cb/st.o -------------------------------------------------------------------------------- /win.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | enum win_mode { 4 | MODE_VISIBLE = 1 << 0, 5 | MODE_FOCUSED = 1 << 1, 6 | MODE_APPKEYPAD = 1 << 2, 7 | MODE_MOUSEBTN = 1 << 3, 8 | MODE_MOUSEMOTION = 1 << 4, 9 | MODE_REVERSE = 1 << 5, 10 | MODE_KBDLOCK = 1 << 6, 11 | MODE_HIDE = 1 << 7, 12 | MODE_APPCURSOR = 1 << 8, 13 | MODE_MOUSESGR = 1 << 9, 14 | MODE_8BIT = 1 << 10, 15 | MODE_BLINK = 1 << 11, 16 | MODE_FBLINK = 1 << 12, 17 | MODE_FOCUS = 1 << 13, 18 | MODE_MOUSEX10 = 1 << 14, 19 | MODE_MOUSEMANY = 1 << 15, 20 | MODE_BRCKTPASTE = 1 << 16, 21 | MODE_NUMLOCK = 1 << 17, 22 | MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 23 | |MODE_MOUSEMANY, 24 | }; 25 | 26 | void xbell(void); 27 | void xclipcopy(void); 28 | void xdrawcursor(int, int, Glyph, int, int, Glyph); 29 | void xdrawline(Line, int, int, int); 30 | void xfinishdraw(void); 31 | void xloadcols(void); 32 | int xsetcolorname(int, const char *); 33 | void xsettitle(char *); 34 | int xsetcursor(int); 35 | void xsetmode(int, unsigned int); 36 | void xsetpointermotion(int); 37 | void xsetsel(char *); 38 | int xstartdraw(void); 39 | void xximspot(int, int); 40 | -------------------------------------------------------------------------------- /x.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license 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 | 19 | static char *argv0; 20 | #include "arg.h" 21 | #include "st.h" 22 | #include "win.h" 23 | 24 | /* types used in config.h */ 25 | typedef struct { 26 | uint mod; 27 | KeySym keysym; 28 | void (*func)(const Arg *); 29 | const Arg arg; 30 | } Shortcut; 31 | 32 | typedef struct { 33 | uint b; 34 | uint mask; 35 | char *s; 36 | } MouseShortcut; 37 | 38 | typedef struct { 39 | KeySym k; 40 | uint mask; 41 | char *s; 42 | /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ 43 | signed char appkey; /* application keypad */ 44 | signed char appcursor; /* application cursor */ 45 | } Key; 46 | 47 | /* Xresources preferences */ 48 | enum resource_type { 49 | STRING = 0, 50 | INTEGER = 1, 51 | FLOAT = 2 52 | }; 53 | 54 | typedef struct { 55 | char *name; 56 | enum resource_type type; 57 | void *dst; 58 | } ResourcePref; 59 | 60 | /* X modifiers */ 61 | #define XK_ANY_MOD UINT_MAX 62 | #define XK_NO_MOD 0 63 | #define XK_SWITCH_MOD (1<<13) 64 | 65 | /* function definitions used in config.h */ 66 | static void clipcopy(const Arg *); 67 | static void clippaste(const Arg *); 68 | static void numlock(const Arg *); 69 | static void selpaste(const Arg *); 70 | static void zoom(const Arg *); 71 | static void zoomabs(const Arg *); 72 | static void zoomreset(const Arg *); 73 | 74 | /* config.h for applying patches and the configuration. */ 75 | #include "config.h" 76 | 77 | /* XEMBED messages */ 78 | #define XEMBED_FOCUS_IN 4 79 | #define XEMBED_FOCUS_OUT 5 80 | 81 | /* macros */ 82 | #define IS_SET(flag) ((win.mode & (flag)) != 0) 83 | #define TRUERED(x) (((x) & 0xff0000) >> 8) 84 | #define TRUEGREEN(x) (((x) & 0xff00)) 85 | #define TRUEBLUE(x) (((x) & 0xff) << 8) 86 | 87 | typedef XftDraw *Draw; 88 | typedef XftColor Color; 89 | typedef XftGlyphFontSpec GlyphFontSpec; 90 | 91 | /* Purely graphic info */ 92 | typedef struct { 93 | int tw, th; /* tty width and height */ 94 | int w, h; /* window width and height */ 95 | int hborderpx, vborderpx; 96 | int ch; /* char height */ 97 | int cw; /* char width */ 98 | int cyo; /* char y offset */ 99 | int mode; /* window state/mode flags */ 100 | int cursor; /* cursor style */ 101 | } TermWindow; 102 | 103 | typedef struct { 104 | Display *dpy; 105 | Colormap cmap; 106 | Window win; 107 | Drawable buf; 108 | GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 109 | Atom xembed, wmdeletewin, netwmname, netwmpid; 110 | XIM xim; 111 | XIC xic; 112 | Draw draw; 113 | Visual *vis; 114 | XSetWindowAttributes attrs; 115 | int scr; 116 | int isfixed; /* is fixed geometry? */ 117 | int depth; /* bit depth */ 118 | int l, t; /* left and top offset */ 119 | int gm; /* geometry mask */ 120 | } XWindow; 121 | 122 | typedef struct { 123 | Atom xtarget; 124 | char *primary, *clipboard; 125 | struct timespec tclick1; 126 | struct timespec tclick2; 127 | } XSelection; 128 | 129 | /* Font structure */ 130 | #define Font Font_ 131 | typedef struct { 132 | int height; 133 | int width; 134 | int ascent; 135 | int descent; 136 | int badslant; 137 | int badweight; 138 | short lbearing; 139 | short rbearing; 140 | XftFont *match; 141 | FcFontSet *set; 142 | FcPattern *pattern; 143 | } Font; 144 | 145 | /* Drawing Context */ 146 | typedef struct { 147 | Color *col; 148 | size_t collen; 149 | Font font, bfont, ifont, ibfont; 150 | GC gc; 151 | } DC; 152 | 153 | static inline ushort sixd_to_16bit(int); 154 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 155 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 156 | static void xdrawglyph(Glyph, int, int); 157 | static void xclear(int, int, int, int); 158 | static int xgeommasktogravity(int); 159 | static void ximopen(Display *); 160 | static void ximinstantiate(Display *, XPointer, XPointer); 161 | static void ximdestroy(XIM, XPointer, XPointer); 162 | static void xinit(int, int); 163 | static void cresize(int, int); 164 | static void xresize(int, int); 165 | static void xhints(void); 166 | static int xloadcolor(int, const char *, Color *); 167 | static int xloadfont(Font *, FcPattern *); 168 | static void xloadfonts(char *, double); 169 | static void xunloadfont(Font *); 170 | static void xunloadfonts(void); 171 | static void xsetenv(void); 172 | static void xseturgency(int); 173 | static int evcol(XEvent *); 174 | static int evrow(XEvent *); 175 | 176 | static void expose(XEvent *); 177 | static void visibility(XEvent *); 178 | static void unmap(XEvent *); 179 | static void kpress(XEvent *); 180 | static void cmessage(XEvent *); 181 | static void resize(XEvent *); 182 | static void focus(XEvent *); 183 | static void brelease(XEvent *); 184 | static void bpress(XEvent *); 185 | static void bmotion(XEvent *); 186 | static void propnotify(XEvent *); 187 | static void selnotify(XEvent *); 188 | static void selclear_(XEvent *); 189 | static void selrequest(XEvent *); 190 | static void setsel(char *, Time); 191 | static void mousesel(XEvent *, int); 192 | static void mousereport(XEvent *); 193 | static char *kmap(KeySym, uint); 194 | static int match(uint, uint); 195 | 196 | static void run(void); 197 | static void usage(void); 198 | 199 | static void (*handler[LASTEvent])(XEvent *) = { 200 | [KeyPress] = kpress, 201 | [ClientMessage] = cmessage, 202 | [ConfigureNotify] = resize, 203 | [VisibilityNotify] = visibility, 204 | [UnmapNotify] = unmap, 205 | [Expose] = expose, 206 | [FocusIn] = focus, 207 | [FocusOut] = focus, 208 | [MotionNotify] = bmotion, 209 | [ButtonPress] = bpress, 210 | [ButtonRelease] = brelease, 211 | /* 212 | * Uncomment if you want the selection to disappear when you select something 213 | * different in another window. 214 | */ 215 | /* [SelectionClear] = selclear_, */ 216 | [SelectionNotify] = selnotify, 217 | /* 218 | * PropertyNotify is only turned on when there is some INCR transfer happening 219 | * for the selection retrieval. 220 | */ 221 | [PropertyNotify] = propnotify, 222 | [SelectionRequest] = selrequest, 223 | }; 224 | 225 | /* Globals */ 226 | static DC dc; 227 | static XWindow xw; 228 | static XSelection xsel; 229 | static TermWindow win; 230 | 231 | /* Font Ring Cache */ 232 | enum { 233 | FRC_NORMAL, 234 | FRC_ITALIC, 235 | FRC_BOLD, 236 | FRC_ITALICBOLD 237 | }; 238 | 239 | typedef struct { 240 | XftFont *font; 241 | int flags; 242 | Rune unicodep; 243 | } Fontcache; 244 | 245 | /* Fontcache is an array now. A new font will be appended to the array. */ 246 | static Fontcache *frc = NULL; 247 | static int frclen = 0; 248 | static int frccap = 0; 249 | static char *usedfont = NULL; 250 | static double usedfontsize = 0; 251 | static double defaultfontsize = 0; 252 | 253 | static char *opt_alpha = NULL; 254 | static char *opt_class = NULL; 255 | static char **opt_cmd = NULL; 256 | static char *opt_embed = NULL; 257 | static char *opt_font = NULL; 258 | static char *opt_io = NULL; 259 | static char *opt_line = NULL; 260 | static char *opt_name = NULL; 261 | static char *opt_title = NULL; 262 | 263 | static int oldbutton = 3; /* button event on startup: 3 = release */ 264 | 265 | void 266 | clipcopy(const Arg *dummy) 267 | { 268 | Atom clipboard; 269 | 270 | free(xsel.clipboard); 271 | xsel.clipboard = NULL; 272 | 273 | if (xsel.primary != NULL) { 274 | xsel.clipboard = xstrdup(xsel.primary); 275 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 276 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 277 | } 278 | } 279 | 280 | void 281 | clippaste(const Arg *dummy) 282 | { 283 | Atom clipboard; 284 | 285 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 286 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, 287 | xw.win, CurrentTime); 288 | } 289 | 290 | void 291 | selpaste(const Arg *dummy) 292 | { 293 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, 294 | xw.win, CurrentTime); 295 | } 296 | 297 | void 298 | numlock(const Arg *dummy) 299 | { 300 | win.mode ^= MODE_NUMLOCK; 301 | } 302 | 303 | void 304 | zoom(const Arg *arg) 305 | { 306 | Arg larg; 307 | 308 | larg.f = usedfontsize + arg->f; 309 | zoomabs(&larg); 310 | } 311 | 312 | void 313 | zoomabs(const Arg *arg) 314 | { 315 | xunloadfonts(); 316 | xloadfonts(usedfont, arg->f); 317 | cresize(0, 0); 318 | redraw(); 319 | xhints(); 320 | } 321 | 322 | void 323 | zoomreset(const Arg *arg) 324 | { 325 | Arg larg; 326 | 327 | if (defaultfontsize > 0) { 328 | larg.f = defaultfontsize; 329 | zoomabs(&larg); 330 | } 331 | } 332 | 333 | int 334 | evcol(XEvent *e) 335 | { 336 | int x = e->xbutton.x - win.hborderpx; 337 | LIMIT(x, 0, win.tw - 1); 338 | return x / win.cw; 339 | } 340 | 341 | int 342 | evrow(XEvent *e) 343 | { 344 | int y = e->xbutton.y - win.vborderpx; 345 | LIMIT(y, 0, win.th - 1); 346 | return y / win.ch; 347 | } 348 | 349 | void 350 | mousesel(XEvent *e, int done) 351 | { 352 | int type, seltype = SEL_REGULAR; 353 | uint state = e->xbutton.state & ~(Button1Mask | forceselmod); 354 | 355 | for (type = 1; type < LEN(selmasks); ++type) { 356 | if (match(selmasks[type], state)) { 357 | seltype = type; 358 | break; 359 | } 360 | } 361 | selextend(evcol(e), evrow(e), seltype, done); 362 | if (done) 363 | setsel(getsel(), e->xbutton.time); 364 | } 365 | 366 | void 367 | mousereport(XEvent *e) 368 | { 369 | int len, x = evcol(e), y = evrow(e), 370 | button = e->xbutton.button, state = e->xbutton.state; 371 | char buf[40]; 372 | static int ox, oy; 373 | 374 | /* from urxvt */ 375 | if (e->xbutton.type == MotionNotify) { 376 | if (x == ox && y == oy) 377 | return; 378 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 379 | return; 380 | /* MOUSE_MOTION: no reporting if no button is pressed */ 381 | if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) 382 | return; 383 | 384 | button = oldbutton + 32; 385 | ox = x; 386 | oy = y; 387 | } else { 388 | if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { 389 | button = 3; 390 | } else { 391 | button -= Button1; 392 | if (button >= 3) 393 | button += 64 - 3; 394 | } 395 | if (e->xbutton.type == ButtonPress) { 396 | oldbutton = button; 397 | ox = x; 398 | oy = y; 399 | } else if (e->xbutton.type == ButtonRelease) { 400 | oldbutton = 3; 401 | /* MODE_MOUSEX10: no button release reporting */ 402 | if (IS_SET(MODE_MOUSEX10)) 403 | return; 404 | if (button == 64 || button == 65) 405 | return; 406 | } 407 | } 408 | 409 | if (!IS_SET(MODE_MOUSEX10)) { 410 | button += ((state & ShiftMask ) ? 4 : 0) 411 | + ((state & Mod4Mask ) ? 8 : 0) 412 | + ((state & ControlMask) ? 16 : 0); 413 | } 414 | 415 | if (IS_SET(MODE_MOUSESGR)) { 416 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 417 | button, x+1, y+1, 418 | e->xbutton.type == ButtonRelease ? 'm' : 'M'); 419 | } else if (x < 223 && y < 223) { 420 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 421 | 32+button, 32+x+1, 32+y+1); 422 | } else { 423 | return; 424 | } 425 | 426 | ttywrite(buf, len, 0); 427 | } 428 | 429 | void 430 | bpress(XEvent *e) 431 | { 432 | struct timespec now; 433 | MouseShortcut *ms; 434 | MouseKey *mk; 435 | int snap; 436 | 437 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 438 | mousereport(e); 439 | return; 440 | } 441 | 442 | for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 443 | if (e->xbutton.button == ms->b 444 | && match(ms->mask, e->xbutton.state)) { 445 | ttywrite(ms->s, strlen(ms->s), 1); 446 | return; 447 | } 448 | } 449 | 450 | for (mk = mkeys; mk < mkeys + LEN(mkeys); mk++) { 451 | if (e->xbutton.button == mk->b 452 | && match(mk->mask, e->xbutton.state)) { 453 | mk->func(&mk->arg); 454 | return; 455 | } 456 | } 457 | 458 | if (e->xbutton.button == Button1) { 459 | /* 460 | * If the user clicks below predefined timeouts specific 461 | * snapping behaviour is exposed. 462 | */ 463 | clock_gettime(CLOCK_MONOTONIC, &now); 464 | if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { 465 | snap = SNAP_LINE; 466 | } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { 467 | snap = SNAP_WORD; 468 | } else { 469 | snap = 0; 470 | } 471 | xsel.tclick2 = xsel.tclick1; 472 | xsel.tclick1 = now; 473 | 474 | selstart(evcol(e), evrow(e), snap); 475 | } 476 | } 477 | 478 | void 479 | propnotify(XEvent *e) 480 | { 481 | XPropertyEvent *xpev; 482 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 483 | 484 | xpev = &e->xproperty; 485 | if (xpev->state == PropertyNewValue && 486 | (xpev->atom == XA_PRIMARY || 487 | xpev->atom == clipboard)) { 488 | selnotify(e); 489 | } 490 | } 491 | 492 | void 493 | selnotify(XEvent *e) 494 | { 495 | ulong nitems, ofs, rem; 496 | int format; 497 | uchar *data, *last, *repl; 498 | Atom type, incratom, property = None; 499 | 500 | incratom = XInternAtom(xw.dpy, "INCR", 0); 501 | 502 | ofs = 0; 503 | if (e->type == SelectionNotify) 504 | property = e->xselection.property; 505 | else if (e->type == PropertyNotify) 506 | property = e->xproperty.atom; 507 | 508 | if (property == None) 509 | return; 510 | 511 | do { 512 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 513 | BUFSIZ/4, False, AnyPropertyType, 514 | &type, &format, &nitems, &rem, 515 | &data)) { 516 | fprintf(stderr, "Clipboard allocation failed\n"); 517 | return; 518 | } 519 | 520 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 521 | /* 522 | * If there is some PropertyNotify with no data, then 523 | * this is the signal of the selection owner that all 524 | * data has been transferred. We won't need to receive 525 | * PropertyNotify events anymore. 526 | */ 527 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 528 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 529 | &xw.attrs); 530 | } 531 | 532 | if (type == incratom) { 533 | /* 534 | * Activate the PropertyNotify events so we receive 535 | * when the selection owner does send us the next 536 | * chunk of data. 537 | */ 538 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 539 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 540 | &xw.attrs); 541 | 542 | /* 543 | * Deleting the property is the transfer start signal. 544 | */ 545 | XDeleteProperty(xw.dpy, xw.win, (int)property); 546 | continue; 547 | } 548 | 549 | /* 550 | * As seen in getsel: 551 | * Line endings are inconsistent in the terminal and GUI world 552 | * copy and pasting. When receiving some selection data, 553 | * replace all '\n' with '\r'. 554 | * FIXME: Fix the computer world. 555 | */ 556 | repl = data; 557 | last = data + nitems * format / 8; 558 | while ((repl = memchr(repl, '\n', last - repl))) { 559 | *repl++ = '\r'; 560 | } 561 | 562 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 563 | ttywrite("\033[200~", 6, 0); 564 | ttywrite((char *)data, nitems * format / 8, 1); 565 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 566 | ttywrite("\033[201~", 6, 0); 567 | XFree(data); 568 | /* number of 32-bit chunks returned */ 569 | ofs += nitems * format / 32; 570 | } while (rem > 0); 571 | 572 | /* 573 | * Deleting the property again tells the selection owner to send the 574 | * next data chunk in the property. 575 | */ 576 | XDeleteProperty(xw.dpy, xw.win, (int)property); 577 | } 578 | 579 | void 580 | xclipcopy(void) 581 | { 582 | clipcopy(NULL); 583 | } 584 | 585 | void 586 | selclear_(XEvent *e) 587 | { 588 | selclear(); 589 | } 590 | 591 | void 592 | selrequest(XEvent *e) 593 | { 594 | XSelectionRequestEvent *xsre; 595 | XSelectionEvent xev; 596 | Atom xa_targets, string, clipboard; 597 | char *seltext; 598 | 599 | xsre = (XSelectionRequestEvent *) e; 600 | xev.type = SelectionNotify; 601 | xev.requestor = xsre->requestor; 602 | xev.selection = xsre->selection; 603 | xev.target = xsre->target; 604 | xev.time = xsre->time; 605 | if (xsre->property == None) 606 | xsre->property = xsre->target; 607 | 608 | /* reject */ 609 | xev.property = None; 610 | 611 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 612 | if (xsre->target == xa_targets) { 613 | /* respond with the supported type */ 614 | string = xsel.xtarget; 615 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 616 | XA_ATOM, 32, PropModeReplace, 617 | (uchar *) &string, 1); 618 | xev.property = xsre->property; 619 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 620 | /* 621 | * xith XA_STRING non ascii characters may be incorrect in the 622 | * requestor. It is not our problem, use utf8. 623 | */ 624 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 625 | if (xsre->selection == XA_PRIMARY) { 626 | seltext = xsel.primary; 627 | } else if (xsre->selection == clipboard) { 628 | seltext = xsel.clipboard; 629 | } else { 630 | fprintf(stderr, 631 | "Unhandled clipboard selection 0x%lx\n", 632 | xsre->selection); 633 | return; 634 | } 635 | if (seltext != NULL) { 636 | XChangeProperty(xsre->display, xsre->requestor, 637 | xsre->property, xsre->target, 638 | 8, PropModeReplace, 639 | (uchar *)seltext, strlen(seltext)); 640 | xev.property = xsre->property; 641 | } 642 | } 643 | 644 | /* all done, send a notification to the listener */ 645 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 646 | fprintf(stderr, "Error sending SelectionNotify event\n"); 647 | } 648 | 649 | void 650 | setsel(char *str, Time t) 651 | { 652 | if (!str) 653 | return; 654 | 655 | free(xsel.primary); 656 | xsel.primary = str; 657 | 658 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 659 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 660 | selclear(); 661 | 662 | clipcopy(NULL); 663 | } 664 | 665 | void 666 | xsetsel(char *str) 667 | { 668 | setsel(str, CurrentTime); 669 | } 670 | 671 | void 672 | brelease(XEvent *e) 673 | { 674 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 675 | mousereport(e); 676 | return; 677 | } 678 | 679 | if (e->xbutton.button == Button2) 680 | clippaste(NULL); 681 | else if (e->xbutton.button == Button1) 682 | mousesel(e, 1); 683 | } 684 | 685 | void 686 | bmotion(XEvent *e) 687 | { 688 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 689 | mousereport(e); 690 | return; 691 | } 692 | 693 | mousesel(e, 0); 694 | } 695 | 696 | void 697 | cresize(int width, int height) 698 | { 699 | int col, row; 700 | 701 | if (width != 0) 702 | win.w = width; 703 | if (height != 0) 704 | win.h = height; 705 | 706 | col = (win.w - 2 * borderpx) / win.cw; 707 | row = (win.h - 2 * borderpx) / win.ch; 708 | col = MAX(1, col); 709 | row = MAX(1, row); 710 | 711 | win.hborderpx = (win.w - col * win.cw) / 2; 712 | win.vborderpx = (win.h - row * win.ch) / 2; 713 | 714 | tresize(col, row); 715 | xresize(col, row); 716 | ttyresize(win.tw, win.th); 717 | } 718 | 719 | void 720 | xresize(int col, int row) 721 | { 722 | win.tw = col * win.cw; 723 | win.th = row * win.ch; 724 | 725 | XFreePixmap(xw.dpy, xw.buf); 726 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 727 | xw.depth); 728 | XftDrawChange(xw.draw, xw.buf); 729 | xclear(0, 0, win.w, win.h); 730 | 731 | /* resize to new width */ 732 | xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); 733 | } 734 | 735 | ushort 736 | sixd_to_16bit(int x) 737 | { 738 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; 739 | } 740 | 741 | int 742 | xloadcolor(int i, const char *name, Color *ncolor) 743 | { 744 | XRenderColor color = { .alpha = 0xffff }; 745 | 746 | if (!name) { 747 | if (BETWEEN(i, 16, 255)) { /* 256 color */ 748 | if (i < 6*6*6+16) { /* same colors as xterm */ 749 | color.red = sixd_to_16bit( ((i-16)/36)%6 ); 750 | color.green = sixd_to_16bit( ((i-16)/6) %6 ); 751 | color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 752 | } else { /* greyscale */ 753 | color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 754 | color.green = color.blue = color.red; 755 | } 756 | return XftColorAllocValue(xw.dpy, xw.vis, 757 | xw.cmap, &color, ncolor); 758 | } else 759 | name = colorname[i]; 760 | } 761 | 762 | return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 763 | } 764 | 765 | void 766 | xloadcols(void) 767 | { 768 | int i; 769 | static int loaded; 770 | Color *cp; 771 | 772 | if (loaded) { 773 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) 774 | XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); 775 | } else { 776 | dc.collen = MAX(LEN(colorname), 256); 777 | dc.col = xmalloc(dc.collen * sizeof(Color)); 778 | } 779 | 780 | for (i = 0; i < dc.collen; i++) 781 | if (!xloadcolor(i, NULL, &dc.col[i])) { 782 | if (colorname[i]) 783 | die("could not allocate color '%s'\n", colorname[i]); 784 | else 785 | die("could not allocate color %d\n", i); 786 | } 787 | 788 | /* set alpha value of bg color */ 789 | if (opt_alpha) 790 | alpha = strtof(opt_alpha, NULL); 791 | dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); 792 | dc.col[defaultbg].color.red = 793 | ((unsigned short)(dc.col[defaultbg].color.red * alpha)) & 0xff00; 794 | dc.col[defaultbg].color.green = 795 | ((unsigned short)(dc.col[defaultbg].color.green * alpha)) & 0xff00; 796 | dc.col[defaultbg].color.blue = 797 | ((unsigned short)(dc.col[defaultbg].color.blue * alpha)) & 0xff00; 798 | dc.col[defaultbg].pixel &= 0x00FFFFFF; 799 | dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; 800 | loaded = 1; 801 | } 802 | 803 | int 804 | xsetcolorname(int x, const char *name) 805 | { 806 | Color ncolor; 807 | 808 | if (!BETWEEN(x, 0, dc.collen)) 809 | return 1; 810 | 811 | if (!xloadcolor(x, name, &ncolor)) 812 | return 1; 813 | 814 | XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 815 | dc.col[x] = ncolor; 816 | 817 | return 0; 818 | } 819 | 820 | /* 821 | * Absolute coordinates. 822 | */ 823 | void 824 | xclear(int x1, int y1, int x2, int y2) 825 | { 826 | XftDrawRect(xw.draw, 827 | &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 828 | x1, y1, x2-x1, y2-y1); 829 | } 830 | 831 | void 832 | xhints(void) 833 | { 834 | XClassHint class = {opt_name ? opt_name : "st", 835 | opt_class ? opt_class : "St"}; 836 | XWMHints wm = {.flags = InputHint, .input = 1}; 837 | XSizeHints *sizeh; 838 | 839 | sizeh = XAllocSizeHints(); 840 | 841 | sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 842 | sizeh->height = win.h; 843 | sizeh->width = win.w; 844 | sizeh->height_inc = 1; 845 | sizeh->width_inc = 1; 846 | sizeh->base_height = 2 * borderpx; 847 | sizeh->base_width = 2 * borderpx; 848 | sizeh->min_height = win.ch + 2 * borderpx; 849 | sizeh->min_width = win.cw + 2 * borderpx; 850 | if (xw.isfixed) { 851 | sizeh->flags |= PMaxSize; 852 | sizeh->min_width = sizeh->max_width = win.w; 853 | sizeh->min_height = sizeh->max_height = win.h; 854 | } 855 | if (xw.gm & (XValue|YValue)) { 856 | sizeh->flags |= USPosition | PWinGravity; 857 | sizeh->x = xw.l; 858 | sizeh->y = xw.t; 859 | sizeh->win_gravity = xgeommasktogravity(xw.gm); 860 | } 861 | 862 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 863 | &class); 864 | XFree(sizeh); 865 | } 866 | 867 | int 868 | xgeommasktogravity(int mask) 869 | { 870 | switch (mask & (XNegative|YNegative)) { 871 | case 0: 872 | return NorthWestGravity; 873 | case XNegative: 874 | return NorthEastGravity; 875 | case YNegative: 876 | return SouthWestGravity; 877 | } 878 | 879 | return SouthEastGravity; 880 | } 881 | 882 | int 883 | xloadfont(Font *f, FcPattern *pattern) 884 | { 885 | FcPattern *configured; 886 | FcPattern *match; 887 | FcResult result; 888 | XGlyphInfo extents; 889 | int wantattr, haveattr; 890 | 891 | /* 892 | * Manually configure instead of calling XftMatchFont 893 | * so that we can use the configured pattern for 894 | * "missing glyph" lookups. 895 | */ 896 | configured = FcPatternDuplicate(pattern); 897 | if (!configured) 898 | return 1; 899 | 900 | FcConfigSubstitute(NULL, configured, FcMatchPattern); 901 | XftDefaultSubstitute(xw.dpy, xw.scr, configured); 902 | 903 | match = FcFontMatch(NULL, configured, &result); 904 | if (!match) { 905 | FcPatternDestroy(configured); 906 | return 1; 907 | } 908 | 909 | if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 910 | FcPatternDestroy(configured); 911 | FcPatternDestroy(match); 912 | return 1; 913 | } 914 | 915 | if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 916 | XftResultMatch)) { 917 | /* 918 | * Check if xft was unable to find a font with the appropriate 919 | * slant but gave us one anyway. Try to mitigate. 920 | */ 921 | if ((XftPatternGetInteger(f->match->pattern, "slant", 0, 922 | &haveattr) != XftResultMatch) || haveattr < wantattr) { 923 | f->badslant = 1; 924 | fputs("font slant does not match\n", stderr); 925 | } 926 | } 927 | 928 | if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 929 | XftResultMatch)) { 930 | if ((XftPatternGetInteger(f->match->pattern, "weight", 0, 931 | &haveattr) != XftResultMatch) || haveattr != wantattr) { 932 | f->badweight = 1; 933 | fputs("font weight does not match\n", stderr); 934 | } 935 | } 936 | 937 | XftTextExtentsUtf8(xw.dpy, f->match, 938 | (const FcChar8 *) ascii_printable, 939 | strlen(ascii_printable), &extents); 940 | 941 | f->set = NULL; 942 | f->pattern = configured; 943 | 944 | f->ascent = f->match->ascent; 945 | f->descent = f->match->descent; 946 | f->lbearing = 0; 947 | f->rbearing = f->match->max_advance_width; 948 | 949 | f->height = f->ascent + f->descent; 950 | f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 951 | 952 | return 0; 953 | } 954 | 955 | void 956 | xloadfonts(char *fontstr, double fontsize) 957 | { 958 | FcPattern *pattern; 959 | double fontval; 960 | 961 | if (fontstr[0] == '-') 962 | pattern = XftXlfdParse(fontstr, False, False); 963 | else 964 | pattern = FcNameParse((FcChar8 *)fontstr); 965 | 966 | if (!pattern) 967 | die("can't open font %s\n", fontstr); 968 | 969 | if (fontsize > 1) { 970 | FcPatternDel(pattern, FC_PIXEL_SIZE); 971 | FcPatternDel(pattern, FC_SIZE); 972 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 973 | usedfontsize = fontsize; 974 | } else { 975 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 976 | FcResultMatch) { 977 | usedfontsize = fontval; 978 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 979 | FcResultMatch) { 980 | usedfontsize = -1; 981 | } else { 982 | /* 983 | * Default font size is 12, if none given. This is to 984 | * have a known usedfontsize value. 985 | */ 986 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 987 | usedfontsize = 12; 988 | } 989 | defaultfontsize = usedfontsize; 990 | } 991 | 992 | if (xloadfont(&dc.font, pattern)) 993 | die("can't open font %s\n", fontstr); 994 | 995 | if (usedfontsize < 0) { 996 | FcPatternGetDouble(dc.font.match->pattern, 997 | FC_PIXEL_SIZE, 0, &fontval); 998 | usedfontsize = fontval; 999 | if (fontsize == 0) 1000 | defaultfontsize = fontval; 1001 | } 1002 | 1003 | /* Setting character width and height. */ 1004 | win.cw = ceilf(dc.font.width * cwscale); 1005 | win.ch = ceilf(dc.font.height * chscale); 1006 | win.cyo = ceilf(dc.font.height * (chscale - 1) / 2); 1007 | 1008 | FcPatternDel(pattern, FC_SLANT); 1009 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1010 | if (xloadfont(&dc.ifont, pattern)) 1011 | die("can't open font %s\n", fontstr); 1012 | 1013 | FcPatternDel(pattern, FC_WEIGHT); 1014 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1015 | if (xloadfont(&dc.ibfont, pattern)) 1016 | die("can't open font %s\n", fontstr); 1017 | 1018 | FcPatternDel(pattern, FC_SLANT); 1019 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1020 | if (xloadfont(&dc.bfont, pattern)) 1021 | die("can't open font %s\n", fontstr); 1022 | 1023 | FcPatternDestroy(pattern); 1024 | } 1025 | 1026 | void 1027 | xunloadfont(Font *f) 1028 | { 1029 | XftFontClose(xw.dpy, f->match); 1030 | FcPatternDestroy(f->pattern); 1031 | if (f->set) 1032 | FcFontSetDestroy(f->set); 1033 | } 1034 | 1035 | void 1036 | xunloadfonts(void) 1037 | { 1038 | /* Free the loaded fonts in the font cache. */ 1039 | while (frclen > 0) 1040 | XftFontClose(xw.dpy, frc[--frclen].font); 1041 | 1042 | xunloadfont(&dc.font); 1043 | xunloadfont(&dc.bfont); 1044 | xunloadfont(&dc.ifont); 1045 | xunloadfont(&dc.ibfont); 1046 | } 1047 | 1048 | void 1049 | ximopen(Display *dpy) 1050 | { 1051 | XIMCallback destroy = { .client_data = NULL, .callback = ximdestroy }; 1052 | 1053 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 1054 | XSetLocaleModifiers("@im=local"); 1055 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 1056 | XSetLocaleModifiers("@im="); 1057 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) 1058 | die("XOpenIM failed. Could not open input device.\n"); 1059 | } 1060 | } 1061 | if (XSetIMValues(xw.xim, XNDestroyCallback, &destroy, NULL) != NULL) 1062 | die("XSetIMValues failed. Could not set input method value.\n"); 1063 | xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 1064 | XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); 1065 | if (xw.xic == NULL) 1066 | die("XCreateIC failed. Could not obtain input method.\n"); 1067 | } 1068 | 1069 | void 1070 | ximinstantiate(Display *dpy, XPointer client, XPointer call) 1071 | { 1072 | ximopen(dpy); 1073 | XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1074 | ximinstantiate, NULL); 1075 | } 1076 | 1077 | void 1078 | ximdestroy(XIM xim, XPointer client, XPointer call) 1079 | { 1080 | xw.xim = NULL; 1081 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1082 | ximinstantiate, NULL); 1083 | } 1084 | 1085 | void 1086 | xinit(int cols, int rows) 1087 | { 1088 | XGCValues gcvalues; 1089 | Cursor cursor; 1090 | Window parent; 1091 | pid_t thispid = getpid(); 1092 | XColor xmousefg, xmousebg; 1093 | XWindowAttributes attr; 1094 | XVisualInfo vis; 1095 | 1096 | xw.scr = XDefaultScreen(xw.dpy); 1097 | 1098 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { 1099 | parent = XRootWindow(xw.dpy, xw.scr); 1100 | xw.depth = 32; 1101 | } else { 1102 | XGetWindowAttributes(xw.dpy, parent, &attr); 1103 | xw.depth = attr.depth; 1104 | } 1105 | 1106 | XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); 1107 | xw.vis = vis.visual; 1108 | 1109 | /* font */ 1110 | if (!FcInit()) 1111 | die("could not init fontconfig.\n"); 1112 | 1113 | usedfont = (opt_font == NULL)? font : opt_font; 1114 | xloadfonts(usedfont, 0); 1115 | 1116 | /* colors */ 1117 | xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); 1118 | xloadcols(); 1119 | 1120 | /* adjust fixed window geometry */ 1121 | win.w = 2 * win.hborderpx + cols * win.cw; 1122 | win.h = 2 * win.vborderpx + rows * win.ch; 1123 | if (xw.gm & XNegative) 1124 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 1125 | if (xw.gm & YNegative) 1126 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 1127 | 1128 | /* Events */ 1129 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; 1130 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; 1131 | xw.attrs.bit_gravity = NorthWestGravity; 1132 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask 1133 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask 1134 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 1135 | xw.attrs.colormap = xw.cmap; 1136 | 1137 | xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, 1138 | win.w, win.h, 0, xw.depth, InputOutput, 1139 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 1140 | | CWEventMask | CWColormap, &xw.attrs); 1141 | 1142 | memset(&gcvalues, 0, sizeof(gcvalues)); 1143 | gcvalues.graphics_exposures = False; 1144 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); 1145 | dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); 1146 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 1147 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 1148 | 1149 | /* font spec buffer */ 1150 | xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); 1151 | 1152 | /* Xft rendering context */ 1153 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 1154 | 1155 | /* input methods */ 1156 | ximopen(xw.dpy); 1157 | 1158 | /* white cursor, black outline */ 1159 | cursor = XCreateFontCursor(xw.dpy, mouseshape); 1160 | XDefineCursor(xw.dpy, xw.win, cursor); 1161 | 1162 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 1163 | xmousefg.red = 0xffff; 1164 | xmousefg.green = 0xffff; 1165 | xmousefg.blue = 0xffff; 1166 | } 1167 | 1168 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 1169 | xmousebg.red = 0x0000; 1170 | xmousebg.green = 0x0000; 1171 | xmousebg.blue = 0x0000; 1172 | } 1173 | 1174 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 1175 | 1176 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 1177 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 1178 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 1179 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 1180 | 1181 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 1182 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 1183 | PropModeReplace, (uchar *)&thispid, 1); 1184 | 1185 | win.mode = MODE_NUMLOCK; 1186 | resettitle(); 1187 | XMapWindow(xw.dpy, xw.win); 1188 | xhints(); 1189 | XSync(xw.dpy, False); 1190 | 1191 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); 1192 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); 1193 | xsel.primary = NULL; 1194 | xsel.clipboard = NULL; 1195 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 1196 | if (xsel.xtarget == None) 1197 | xsel.xtarget = XA_STRING; 1198 | } 1199 | 1200 | int 1201 | xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 1202 | { 1203 | float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; 1204 | ushort mode, prevmode = USHRT_MAX; 1205 | Font *font = &dc.font; 1206 | int frcflags = FRC_NORMAL; 1207 | float runewidth = win.cw; 1208 | Rune rune; 1209 | FT_UInt glyphidx; 1210 | FcResult fcres; 1211 | FcPattern *fcpattern, *fontpattern; 1212 | FcFontSet *fcsets[] = { NULL }; 1213 | FcCharSet *fccharset; 1214 | int i, f, numspecs = 0; 1215 | 1216 | for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i) { 1217 | /* Fetch rune and mode for current glyph. */ 1218 | rune = glyphs[i].u; 1219 | mode = glyphs[i].mode; 1220 | 1221 | /* Skip dummy wide-character spacing. */ 1222 | if (mode == ATTR_WDUMMY) 1223 | continue; 1224 | 1225 | /* Determine font for glyph if different from previous glyph. */ 1226 | if (prevmode != mode) { 1227 | prevmode = mode; 1228 | font = &dc.font; 1229 | frcflags = FRC_NORMAL; 1230 | runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 1231 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 1232 | font = &dc.ibfont; 1233 | frcflags = FRC_ITALICBOLD; 1234 | } else if (mode & ATTR_ITALIC) { 1235 | font = &dc.ifont; 1236 | frcflags = FRC_ITALIC; 1237 | } else if (mode & ATTR_BOLD) { 1238 | font = &dc.bfont; 1239 | frcflags = FRC_BOLD; 1240 | } 1241 | yp = winy + font->ascent + win.cyo; 1242 | } 1243 | 1244 | /* Lookup character index with default font. */ 1245 | glyphidx = XftCharIndex(xw.dpy, font->match, rune); 1246 | if (glyphidx) { 1247 | specs[numspecs].font = font->match; 1248 | specs[numspecs].glyph = glyphidx; 1249 | specs[numspecs].x = (short)xp; 1250 | specs[numspecs].y = (short)yp; 1251 | xp += runewidth; 1252 | numspecs++; 1253 | continue; 1254 | } 1255 | 1256 | /* Fallback on font cache, search the font cache for match. */ 1257 | for (f = 0; f < frclen; f++) { 1258 | glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 1259 | /* Everything correct. */ 1260 | if (glyphidx && frc[f].flags == frcflags) 1261 | break; 1262 | /* We got a default font for a not found glyph. */ 1263 | if (!glyphidx && frc[f].flags == frcflags 1264 | && frc[f].unicodep == rune) { 1265 | break; 1266 | } 1267 | } 1268 | 1269 | /* Nothing was found. Use fontconfig to find matching font. */ 1270 | if (f >= frclen) { 1271 | if (!font->set) 1272 | font->set = FcFontSort(0, font->pattern, 1273 | 1, 0, &fcres); 1274 | fcsets[0] = font->set; 1275 | 1276 | /* 1277 | * Nothing was found in the cache. Now use 1278 | * some dozen of Fontconfig calls to get the 1279 | * font for one single character. 1280 | * 1281 | * Xft and fontconfig are design failures. 1282 | */ 1283 | fcpattern = FcPatternDuplicate(font->pattern); 1284 | fccharset = FcCharSetCreate(); 1285 | 1286 | FcCharSetAddChar(fccharset, rune); 1287 | FcPatternAddCharSet(fcpattern, FC_CHARSET, 1288 | fccharset); 1289 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1290 | 1291 | FcConfigSubstitute(0, fcpattern, 1292 | FcMatchPattern); 1293 | FcDefaultSubstitute(fcpattern); 1294 | 1295 | fontpattern = FcFontSetMatch(0, fcsets, 1, 1296 | fcpattern, &fcres); 1297 | 1298 | /* Allocate memory for the new cache entry. */ 1299 | if (frclen >= frccap) { 1300 | frccap += 16; 1301 | frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1302 | } 1303 | 1304 | frc[frclen].font = XftFontOpenPattern(xw.dpy, 1305 | fontpattern); 1306 | if (!frc[frclen].font) 1307 | die("XftFontOpenPattern failed seeking fallback font: %s\n", 1308 | strerror(errno)); 1309 | frc[frclen].flags = frcflags; 1310 | frc[frclen].unicodep = rune; 1311 | 1312 | glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 1313 | 1314 | f = frclen; 1315 | frclen++; 1316 | 1317 | FcPatternDestroy(fcpattern); 1318 | FcCharSetDestroy(fccharset); 1319 | } 1320 | 1321 | specs[numspecs].font = frc[f].font; 1322 | specs[numspecs].glyph = glyphidx; 1323 | specs[numspecs].x = (short)xp; 1324 | specs[numspecs].y = (short)yp; 1325 | xp += runewidth; 1326 | numspecs++; 1327 | } 1328 | 1329 | return numspecs; 1330 | } 1331 | 1332 | void 1333 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 1334 | { 1335 | int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 1336 | int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, 1337 | width = charlen * win.cw; 1338 | Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 1339 | XRenderColor colfg, colbg; 1340 | XRectangle r; 1341 | 1342 | /* Fallback on color display for attributes not supported by the font */ 1343 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1344 | if (dc.ibfont.badslant || dc.ibfont.badweight) 1345 | base.fg = defaultattr; 1346 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1347 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1348 | base.fg = defaultattr; 1349 | } 1350 | 1351 | if (IS_TRUECOL(base.fg)) { 1352 | colfg.alpha = 0xffff; 1353 | colfg.red = TRUERED(base.fg); 1354 | colfg.green = TRUEGREEN(base.fg); 1355 | colfg.blue = TRUEBLUE(base.fg); 1356 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 1357 | fg = &truefg; 1358 | } else { 1359 | fg = &dc.col[base.fg]; 1360 | } 1361 | 1362 | if (IS_TRUECOL(base.bg)) { 1363 | colbg.alpha = 0xffff; 1364 | colbg.green = TRUEGREEN(base.bg); 1365 | colbg.red = TRUERED(base.bg); 1366 | colbg.blue = TRUEBLUE(base.bg); 1367 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 1368 | bg = &truebg; 1369 | } else { 1370 | bg = &dc.col[base.bg]; 1371 | } 1372 | 1373 | if (IS_SET(MODE_REVERSE)) { 1374 | if (fg == &dc.col[defaultfg]) { 1375 | fg = &dc.col[defaultbg]; 1376 | } else { 1377 | colfg.red = ~fg->color.red; 1378 | colfg.green = ~fg->color.green; 1379 | colfg.blue = ~fg->color.blue; 1380 | colfg.alpha = fg->color.alpha; 1381 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, 1382 | &revfg); 1383 | fg = &revfg; 1384 | } 1385 | 1386 | if (bg == &dc.col[defaultbg]) { 1387 | bg = &dc.col[defaultfg]; 1388 | } else { 1389 | colbg.red = ~bg->color.red; 1390 | colbg.green = ~bg->color.green; 1391 | colbg.blue = ~bg->color.blue; 1392 | colbg.alpha = bg->color.alpha; 1393 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, 1394 | &revbg); 1395 | bg = &revbg; 1396 | } 1397 | } 1398 | 1399 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1400 | colfg.red = fg->color.red / 2; 1401 | colfg.green = fg->color.green / 2; 1402 | colfg.blue = fg->color.blue / 2; 1403 | colfg.alpha = fg->color.alpha; 1404 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1405 | fg = &revfg; 1406 | } 1407 | 1408 | if (base.mode & ATTR_REVERSE) { 1409 | temp = fg; 1410 | fg = bg; 1411 | bg = temp; 1412 | } 1413 | 1414 | if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) 1415 | fg = bg; 1416 | 1417 | if (base.mode & ATTR_INVISIBLE) 1418 | fg = bg; 1419 | 1420 | /* Intelligent cleaning up of the borders. */ 1421 | if (x == 0) { 1422 | xclear(0, (y == 0)? 0 : winy, win.vborderpx, 1423 | winy + win.ch + 1424 | ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); 1425 | } 1426 | if (winx + width >= win.hborderpx + win.tw) { 1427 | xclear(winx + width, (y == 0)? 0 : winy, win.w, 1428 | ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); 1429 | } 1430 | if (y == 0) 1431 | xclear(winx, 0, winx + width, win.hborderpx); 1432 | if (winy + win.ch >= win.vborderpx + win.th) 1433 | xclear(winx, winy + win.ch, winx + width, win.h); 1434 | 1435 | /* Clean up the region we want to draw to. */ 1436 | XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 1437 | 1438 | /* Set the clip region because Xft is sometimes dirty. */ 1439 | r.x = 0; 1440 | r.y = 0; 1441 | r.height = win.ch; 1442 | r.width = width; 1443 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 1444 | 1445 | /* Render the glyphs. */ 1446 | XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1447 | 1448 | /* Render underline and strikethrough. */ 1449 | if (base.mode & ATTR_UNDERLINE) { 1450 | XftDrawRect(xw.draw, fg, winx, winy + win.cyo + dc.font.ascent + 1, 1451 | width, 1); 1452 | } 1453 | 1454 | if (base.mode & ATTR_STRUCK) { 1455 | XftDrawRect(xw.draw, fg, winx, winy + win.cyo + 2 * dc.font.ascent / 3, 1456 | width, 1); 1457 | } 1458 | 1459 | /* Reset clip to none. */ 1460 | XftDrawSetClip(xw.draw, 0); 1461 | } 1462 | 1463 | void 1464 | xdrawglyph(Glyph g, int x, int y) 1465 | { 1466 | int numspecs; 1467 | XftGlyphFontSpec spec; 1468 | 1469 | numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 1470 | xdrawglyphfontspecs(&spec, g, numspecs, x, y); 1471 | } 1472 | 1473 | void 1474 | xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 1475 | { 1476 | Color drawcol; 1477 | 1478 | /* remove the old cursor */ 1479 | if (selected(ox, oy)) 1480 | og.mode ^= ATTR_REVERSE; 1481 | xdrawglyph(og, ox, oy); 1482 | 1483 | if (IS_SET(MODE_HIDE)) 1484 | return; 1485 | 1486 | /* 1487 | * Select the right color for the right mode. 1488 | */ 1489 | g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; 1490 | 1491 | if (IS_SET(MODE_REVERSE)) { 1492 | g.mode |= ATTR_REVERSE; 1493 | g.bg = defaultfg; 1494 | if (selected(cx, cy)) { 1495 | drawcol = dc.col[defaultcs]; 1496 | g.fg = defaultrcs; 1497 | } else { 1498 | drawcol = dc.col[defaultrcs]; 1499 | g.fg = defaultcs; 1500 | } 1501 | } else { 1502 | if (selected(cx, cy)) { 1503 | g.fg = defaultfg; 1504 | g.bg = defaultrcs; 1505 | } else { 1506 | g.fg = defaultbg; 1507 | g.bg = defaultcs; 1508 | } 1509 | drawcol = dc.col[g.bg]; 1510 | } 1511 | 1512 | /* draw the new one */ 1513 | if (IS_SET(MODE_FOCUSED)) { 1514 | switch (win.cursor) { 1515 | case 7: /* st extension: snowman (U+2603) */ 1516 | g.u = 0x2603; 1517 | case 0: /* Blinking Block */ 1518 | case 1: /* Blinking Block (Default) */ 1519 | case 2: /* Steady Block */ 1520 | xdrawglyph(g, cx, cy); 1521 | break; 1522 | case 3: /* Blinking Underline */ 1523 | case 4: /* Steady Underline */ 1524 | XftDrawRect(xw.draw, &drawcol, 1525 | win.hborderpx + cx * win.cw, 1526 | win.vborderpx + (cy + 1) * win.ch - \ 1527 | cursorthickness, 1528 | win.cw, cursorthickness); 1529 | break; 1530 | case 5: /* Blinking bar */ 1531 | case 6: /* Steady bar */ 1532 | XftDrawRect(xw.draw, &drawcol, 1533 | win.hborderpx + cx * win.cw, 1534 | win.vborderpx + cy * win.ch, 1535 | cursorthickness, win.ch); 1536 | break; 1537 | } 1538 | } else { 1539 | XftDrawRect(xw.draw, &drawcol, 1540 | win.hborderpx + cx * win.cw, 1541 | win.vborderpx + cy * win.ch, 1542 | win.cw - 1, 1); 1543 | XftDrawRect(xw.draw, &drawcol, 1544 | win.hborderpx + cx * win.cw, 1545 | win.vborderpx + cy * win.ch, 1546 | 1, win.ch - 1); 1547 | XftDrawRect(xw.draw, &drawcol, 1548 | win.hborderpx + (cx + 1) * win.cw - 1, 1549 | win.vborderpx + cy * win.ch, 1550 | 1, win.ch - 1); 1551 | XftDrawRect(xw.draw, &drawcol, 1552 | win.hborderpx + cx * win.cw, 1553 | win.vborderpx + (cy + 1) * win.ch - 1, 1554 | win.cw, 1); 1555 | } 1556 | } 1557 | 1558 | void 1559 | xsetenv(void) 1560 | { 1561 | char buf[sizeof(long) * 8 + 1]; 1562 | 1563 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1564 | setenv("WINDOWID", buf, 1); 1565 | } 1566 | 1567 | void 1568 | xsettitle(char *p) 1569 | { 1570 | XTextProperty prop; 1571 | DEFAULT(p, opt_title); 1572 | 1573 | Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1574 | &prop); 1575 | XSetWMName(xw.dpy, xw.win, &prop); 1576 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1577 | XFree(prop.value); 1578 | } 1579 | 1580 | int 1581 | xstartdraw(void) 1582 | { 1583 | return IS_SET(MODE_VISIBLE); 1584 | } 1585 | 1586 | void 1587 | xdrawline(Line line, int x1, int y1, int x2) 1588 | { 1589 | int i, x, ox, numspecs; 1590 | Glyph base, new; 1591 | XftGlyphFontSpec *specs = xw.specbuf; 1592 | 1593 | numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 1594 | i = ox = 0; 1595 | for (x = x1; x < x2 && i < numspecs; x++) { 1596 | new = line[x]; 1597 | if (new.mode == ATTR_WDUMMY) 1598 | continue; 1599 | if (selected(x, y1)) 1600 | new.mode ^= ATTR_REVERSE; 1601 | if (i > 0 && ATTRCMP(base, new)) { 1602 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1603 | specs += i; 1604 | numspecs -= i; 1605 | i = 0; 1606 | } 1607 | if (i == 0) { 1608 | ox = x; 1609 | base = new; 1610 | } 1611 | i++; 1612 | } 1613 | if (i > 0) 1614 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1615 | } 1616 | 1617 | void 1618 | xfinishdraw(void) 1619 | { 1620 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 1621 | win.h, 0, 0); 1622 | XSetForeground(xw.dpy, dc.gc, 1623 | dc.col[IS_SET(MODE_REVERSE)? 1624 | defaultfg : defaultbg].pixel); 1625 | } 1626 | 1627 | void 1628 | xximspot(int x, int y) 1629 | { 1630 | XPoint spot = { borderpx + x * win.cw, borderpx + (y + 1) * win.ch }; 1631 | XVaNestedList attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); 1632 | 1633 | XSetICValues(xw.xic, XNPreeditAttributes, attr, NULL); 1634 | XFree(attr); 1635 | } 1636 | 1637 | void 1638 | expose(XEvent *ev) 1639 | { 1640 | redraw(); 1641 | } 1642 | 1643 | void 1644 | visibility(XEvent *ev) 1645 | { 1646 | XVisibilityEvent *e = &ev->xvisibility; 1647 | 1648 | MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); 1649 | } 1650 | 1651 | void 1652 | unmap(XEvent *ev) 1653 | { 1654 | win.mode &= ~MODE_VISIBLE; 1655 | } 1656 | 1657 | void 1658 | xsetpointermotion(int set) 1659 | { 1660 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1661 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1662 | } 1663 | 1664 | void 1665 | xsetmode(int set, unsigned int flags) 1666 | { 1667 | int mode = win.mode; 1668 | MODBIT(win.mode, set, flags); 1669 | if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) 1670 | redraw(); 1671 | } 1672 | 1673 | int 1674 | xsetcursor(int cursor) 1675 | { 1676 | DEFAULT(cursor, 1); 1677 | if (!BETWEEN(cursor, 0, 6)) 1678 | return 1; 1679 | win.cursor = cursor; 1680 | return 0; 1681 | } 1682 | 1683 | void 1684 | xseturgency(int add) 1685 | { 1686 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1687 | 1688 | MODBIT(h->flags, add, XUrgencyHint); 1689 | XSetWMHints(xw.dpy, xw.win, h); 1690 | XFree(h); 1691 | } 1692 | 1693 | void 1694 | xbell(void) 1695 | { 1696 | if (!(IS_SET(MODE_FOCUSED))) 1697 | xseturgency(1); 1698 | if (bellvolume) 1699 | XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 1700 | } 1701 | 1702 | void 1703 | focus(XEvent *ev) 1704 | { 1705 | XFocusChangeEvent *e = &ev->xfocus; 1706 | 1707 | if (e->mode == NotifyGrab) 1708 | return; 1709 | 1710 | if (ev->type == FocusIn) { 1711 | XSetICFocus(xw.xic); 1712 | win.mode |= MODE_FOCUSED; 1713 | xseturgency(0); 1714 | if (IS_SET(MODE_FOCUS)) 1715 | ttywrite("\033[I", 3, 0); 1716 | } else { 1717 | XUnsetICFocus(xw.xic); 1718 | win.mode &= ~MODE_FOCUSED; 1719 | if (IS_SET(MODE_FOCUS)) 1720 | ttywrite("\033[O", 3, 0); 1721 | } 1722 | } 1723 | 1724 | int 1725 | match(uint mask, uint state) 1726 | { 1727 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 1728 | } 1729 | 1730 | char* 1731 | kmap(KeySym k, uint state) 1732 | { 1733 | Key *kp; 1734 | int i; 1735 | 1736 | /* Check for mapped keys out of X11 function keys. */ 1737 | for (i = 0; i < LEN(mappedkeys); i++) { 1738 | if (mappedkeys[i] == k) 1739 | break; 1740 | } 1741 | if (i == LEN(mappedkeys)) { 1742 | if ((k & 0xFFFF) < 0xFD00) 1743 | return NULL; 1744 | } 1745 | 1746 | for (kp = key; kp < key + LEN(key); kp++) { 1747 | if (kp->k != k) 1748 | continue; 1749 | 1750 | if (!match(kp->mask, state)) 1751 | continue; 1752 | 1753 | if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 1754 | continue; 1755 | if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) 1756 | continue; 1757 | 1758 | if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 1759 | continue; 1760 | 1761 | return kp->s; 1762 | } 1763 | 1764 | return NULL; 1765 | } 1766 | 1767 | void 1768 | kpress(XEvent *ev) 1769 | { 1770 | XKeyEvent *e = &ev->xkey; 1771 | KeySym ksym; 1772 | char buf[32], *customkey; 1773 | int len; 1774 | Rune c; 1775 | Status status; 1776 | Shortcut *bp; 1777 | 1778 | if (IS_SET(MODE_KBDLOCK)) 1779 | return; 1780 | 1781 | len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); 1782 | /* 1. shortcuts */ 1783 | for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 1784 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 1785 | bp->func(&(bp->arg)); 1786 | return; 1787 | } 1788 | } 1789 | 1790 | /* 2. custom keys from config.h */ 1791 | if ((customkey = kmap(ksym, e->state))) { 1792 | ttywrite(customkey, strlen(customkey), 1); 1793 | return; 1794 | } 1795 | 1796 | /* 3. composed string from input method */ 1797 | if (len == 0) 1798 | return; 1799 | if (len == 1 && e->state & Mod1Mask) { 1800 | if (IS_SET(MODE_8BIT)) { 1801 | if (*buf < 0177) { 1802 | c = *buf | 0x80; 1803 | len = utf8encode(c, buf); 1804 | } 1805 | } else { 1806 | buf[1] = buf[0]; 1807 | buf[0] = '\033'; 1808 | len = 2; 1809 | } 1810 | } 1811 | ttywrite(buf, len, 1); 1812 | } 1813 | 1814 | void 1815 | cmessage(XEvent *e) 1816 | { 1817 | /* 1818 | * See xembed specs 1819 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 1820 | */ 1821 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 1822 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 1823 | win.mode |= MODE_FOCUSED; 1824 | xseturgency(0); 1825 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 1826 | win.mode &= ~MODE_FOCUSED; 1827 | } 1828 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 1829 | ttyhangup(); 1830 | exit(0); 1831 | } 1832 | } 1833 | 1834 | void 1835 | resize(XEvent *e) 1836 | { 1837 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 1838 | return; 1839 | 1840 | cresize(e->xconfigure.width, e->xconfigure.height); 1841 | } 1842 | 1843 | void 1844 | run(void) 1845 | { 1846 | XEvent ev; 1847 | int w = win.w, h = win.h; 1848 | fd_set rfd; 1849 | int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; 1850 | int ttyfd; 1851 | struct timespec drawtimeout, *tv = NULL, now, last, lastblink; 1852 | long deltatime; 1853 | 1854 | /* Waiting for window mapping */ 1855 | do { 1856 | XNextEvent(xw.dpy, &ev); 1857 | /* 1858 | * This XFilterEvent call is required because of XOpenIM. It 1859 | * does filter out the key event and some client message for 1860 | * the input method too. 1861 | */ 1862 | if (XFilterEvent(&ev, None)) 1863 | continue; 1864 | if (ev.type == ConfigureNotify) { 1865 | w = ev.xconfigure.width; 1866 | h = ev.xconfigure.height; 1867 | } 1868 | } while (ev.type != MapNotify); 1869 | 1870 | ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 1871 | cresize(w, h); 1872 | 1873 | clock_gettime(CLOCK_MONOTONIC, &last); 1874 | lastblink = last; 1875 | 1876 | for (xev = actionfps;;) { 1877 | FD_ZERO(&rfd); 1878 | FD_SET(ttyfd, &rfd); 1879 | FD_SET(xfd, &rfd); 1880 | 1881 | if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 1882 | if (errno == EINTR) 1883 | continue; 1884 | die("select failed: %s\n", strerror(errno)); 1885 | } 1886 | if (FD_ISSET(ttyfd, &rfd)) { 1887 | ttyread(); 1888 | if (blinktimeout) { 1889 | blinkset = tattrset(ATTR_BLINK); 1890 | if (!blinkset) 1891 | MODBIT(win.mode, 0, MODE_BLINK); 1892 | } 1893 | } 1894 | 1895 | if (FD_ISSET(xfd, &rfd)) 1896 | xev = actionfps; 1897 | 1898 | clock_gettime(CLOCK_MONOTONIC, &now); 1899 | drawtimeout.tv_sec = 0; 1900 | drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; 1901 | tv = &drawtimeout; 1902 | 1903 | dodraw = 0; 1904 | if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { 1905 | tsetdirtattr(ATTR_BLINK); 1906 | win.mode ^= MODE_BLINK; 1907 | lastblink = now; 1908 | dodraw = 1; 1909 | } 1910 | deltatime = TIMEDIFF(now, last); 1911 | if (deltatime > 1000 / (xev ? xfps : actionfps)) { 1912 | dodraw = 1; 1913 | last = now; 1914 | } 1915 | 1916 | if (dodraw) { 1917 | while (XPending(xw.dpy)) { 1918 | XNextEvent(xw.dpy, &ev); 1919 | if (XFilterEvent(&ev, None)) 1920 | continue; 1921 | if (handler[ev.type]) 1922 | (handler[ev.type])(&ev); 1923 | } 1924 | 1925 | draw(); 1926 | XFlush(xw.dpy); 1927 | 1928 | if (xev && !FD_ISSET(xfd, &rfd)) 1929 | xev--; 1930 | if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { 1931 | if (blinkset) { 1932 | if (TIMEDIFF(now, lastblink) \ 1933 | > blinktimeout) { 1934 | drawtimeout.tv_nsec = 1000; 1935 | } else { 1936 | drawtimeout.tv_nsec = (1E6 * \ 1937 | (blinktimeout - \ 1938 | TIMEDIFF(now, 1939 | lastblink))); 1940 | } 1941 | drawtimeout.tv_sec = \ 1942 | drawtimeout.tv_nsec / 1E9; 1943 | drawtimeout.tv_nsec %= (long)1E9; 1944 | } else { 1945 | tv = NULL; 1946 | } 1947 | } 1948 | } 1949 | } 1950 | } 1951 | 1952 | int 1953 | resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) 1954 | { 1955 | char **sdst = dst; 1956 | int *idst = dst; 1957 | float *fdst = dst; 1958 | 1959 | char fullname[256]; 1960 | char fullclass[256]; 1961 | char *type; 1962 | XrmValue ret; 1963 | 1964 | snprintf(fullname, sizeof(fullname), "%s.%s", 1965 | opt_name ? opt_name : "st", name); 1966 | snprintf(fullclass, sizeof(fullclass), "%s.%s", 1967 | opt_class ? opt_class : "St", name); 1968 | fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; 1969 | 1970 | XrmGetResource(db, fullname, fullclass, &type, &ret); 1971 | if (ret.addr == NULL || strncmp("String", type, 64)) 1972 | return 1; 1973 | 1974 | switch (rtype) { 1975 | case STRING: 1976 | *sdst = ret.addr; 1977 | break; 1978 | case INTEGER: 1979 | *idst = strtoul(ret.addr, NULL, 10); 1980 | break; 1981 | case FLOAT: 1982 | *fdst = strtof(ret.addr, NULL); 1983 | break; 1984 | } 1985 | return 0; 1986 | } 1987 | 1988 | void 1989 | config_init(void) 1990 | { 1991 | char *resm; 1992 | XrmDatabase db; 1993 | ResourcePref *p; 1994 | 1995 | XrmInitialize(); 1996 | resm = XResourceManagerString(xw.dpy); 1997 | if (!resm) 1998 | return; 1999 | 2000 | db = XrmGetStringDatabase(resm); 2001 | for (p = resources; p < resources + LEN(resources); p++) 2002 | resource_load(db, p->name, p->type, p->dst); 2003 | } 2004 | 2005 | void 2006 | usage(void) 2007 | { 2008 | die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2009 | " [-n name] [-o file]\n" 2010 | " [-T title] [-t title] [-w windowid]" 2011 | " [[-e] command [args ...]]\n" 2012 | " %s [-aiv] [-c class] [-f font] [-g geometry]" 2013 | " [-n name] [-o file]\n" 2014 | " [-T title] [-t title] [-w windowid] -l line" 2015 | " [stty_args ...]\n", argv0, argv0); 2016 | } 2017 | 2018 | int 2019 | main(int argc, char *argv[]) 2020 | { 2021 | xw.l = xw.t = 0; 2022 | xw.isfixed = False; 2023 | win.cursor = cursorshape; 2024 | 2025 | ARGBEGIN { 2026 | case 'a': 2027 | allowaltscreen = 0; 2028 | break; 2029 | case 'A': 2030 | opt_alpha = EARGF(usage()); 2031 | break; 2032 | case 'c': 2033 | opt_class = EARGF(usage()); 2034 | break; 2035 | case 'e': 2036 | if (argc > 0) 2037 | --argc, ++argv; 2038 | goto run; 2039 | case 'f': 2040 | opt_font = EARGF(usage()); 2041 | break; 2042 | case 'g': 2043 | xw.gm = XParseGeometry(EARGF(usage()), 2044 | &xw.l, &xw.t, &cols, &rows); 2045 | break; 2046 | case 'i': 2047 | xw.isfixed = 1; 2048 | break; 2049 | case 'o': 2050 | opt_io = EARGF(usage()); 2051 | break; 2052 | case 'l': 2053 | opt_line = EARGF(usage()); 2054 | break; 2055 | case 'n': 2056 | opt_name = EARGF(usage()); 2057 | break; 2058 | case 't': 2059 | case 'T': 2060 | opt_title = EARGF(usage()); 2061 | break; 2062 | case 'w': 2063 | opt_embed = EARGF(usage()); 2064 | break; 2065 | case 'v': 2066 | die("%s " VERSION "\n", argv0); 2067 | break; 2068 | default: 2069 | usage(); 2070 | } ARGEND; 2071 | 2072 | run: 2073 | if (argc > 0) /* eat all remaining arguments */ 2074 | opt_cmd = argv; 2075 | 2076 | if (!opt_title) 2077 | opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; 2078 | 2079 | setlocale(LC_CTYPE, ""); 2080 | XSetLocaleModifiers(""); 2081 | 2082 | if(!(xw.dpy = XOpenDisplay(NULL))) 2083 | die("Can't open display\n"); 2084 | 2085 | config_init(); 2086 | cols = MAX(cols, 1); 2087 | rows = MAX(rows, 1); 2088 | tnew(cols, rows); 2089 | xinit(cols, rows); 2090 | xsetenv(); 2091 | selinit(); 2092 | run(); 2093 | 2094 | return 0; 2095 | } 2096 | -------------------------------------------------------------------------------- /x.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MentalOutlaw/st/ac9aaa89114e801335b836c263cbf43fa16393cb/x.o --------------------------------------------------------------------------------