├── FAQ ├── LEGACY ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── arg.h ├── config.def.h ├── config.mk ├── st.1 ├── st.c ├── st.h ├── st.info ├── win.h └── x.c /FAQ: -------------------------------------------------------------------------------- 1 | ## Why does st not handle utmp entries? 2 | 3 | Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. 4 | 5 | 6 | ## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! 7 | 8 | It means that st doesn’t have any terminfo entry on your system. Chances are 9 | you did not `make install`. If you just want to test it without installing it, 10 | you can manually run `tic -sx st.info`. 11 | 12 | 13 | ## Nothing works, and nothing is said about an unknown terminal! 14 | 15 | * Some programs just assume they’re running in xterm i.e. they don’t rely on 16 | terminfo. What you see is the current state of the “xterm compliance”. 17 | * Some programs don’t complain about the lacking st description and default to 18 | another terminal. In that case see the question about terminfo. 19 | 20 | 21 | ## How do I scroll back up? 22 | 23 | * Using a terminal multiplexer. 24 | * `st -e tmux` using C-b [ 25 | * `st -e screen` using C-a ESC 26 | * Using the excellent tool of [scroll](https://git.suckless.org/scroll/). 27 | * Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). 28 | 29 | 30 | ## I would like to have utmp and/or scroll functionality by default 31 | 32 | You can add the absolute path of both programs in your config.h file. You only 33 | have to modify the value of utmp and scroll variables. 34 | 35 | 36 | ## Why doesn't the Del key work in some programs? 37 | 38 | Taken from the terminfo manpage: 39 | 40 | If the terminal has a keypad that transmits codes when the keys 41 | are pressed, this information can be given. Note that it is not 42 | possible to handle terminals where the keypad only works in 43 | local (this applies, for example, to the unshifted HP 2621 keys). 44 | If the keypad can be set to transmit or not transmit, give these 45 | codes as smkx and rmkx. Otherwise the keypad is assumed to 46 | always transmit. 47 | 48 | In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that 49 | applications which want to test against keypad keys send these 50 | sequences. 51 | 52 | But buggy applications (like bash and irssi, for example) don't do this. A fast 53 | solution for them is to use the following command: 54 | 55 | $ printf '\033[?1h\033=' >/dev/tty 56 | 57 | or 58 | $ tput smkx 59 | 60 | In the case of bash, readline is used. Readline has a different note in its 61 | manpage about this issue: 62 | 63 | enable-keypad (Off) 64 | When set to On, readline will try to enable the 65 | application keypad when it is called. Some systems 66 | need this to enable arrow keys. 67 | 68 | Adding this option to your .inputrc will fix the keypad problem for all 69 | applications using readline. 70 | 71 | If you are using zsh, then read the zsh FAQ 72 | : 73 | 74 | It should be noted that the O / [ confusion can occur with other keys 75 | such as Home and End. Some systems let you query the key sequences 76 | sent by these keys from the system's terminal database, terminfo. 77 | Unfortunately, the key sequences given there typically apply to the 78 | mode that is not the one zsh uses by default (it's the "application" 79 | mode rather than the "raw" mode). Explaining the use of terminfo is 80 | outside of the scope of this FAQ, but if you wish to use the key 81 | sequences given there you can tell the line editor to turn on 82 | "application" mode when it starts and turn it off when it stops: 83 | 84 | function zle-line-init () { echoti smkx } 85 | function zle-line-finish () { echoti rmkx } 86 | zle -N zle-line-init 87 | zle -N zle-line-finish 88 | 89 | Putting these lines into your .zshrc will fix the problems. 90 | 91 | 92 | ## How can I use meta in 8bit mode? 93 | 94 | St supports meta in 8bit mode, but the default terminfo entry doesn't 95 | use this capability. If you want it, you have to use the 'st-meta' value 96 | in TERM. 97 | 98 | 99 | ## I cannot compile st in OpenBSD 100 | 101 | OpenBSD lacks librt, despite it being mandatory in POSIX 102 | . 103 | If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and 104 | st will compile without any loss of functionality, because all the functions are 105 | included in libc on this platform. 106 | 107 | 108 | ## The Backspace Case 109 | 110 | St is emulating the Linux way of handling backspace being delete and delete being 111 | backspace. 112 | 113 | This is an issue that was discussed in suckless mailing list 114 | . Here is why some old grumpy 115 | terminal users wants its backspace to be how he feels it: 116 | 117 | Well, I am going to comment why I want to change the behaviour 118 | of this key. When ASCII was defined in 1968, communication 119 | with computers was done using punched cards, or hardcopy 120 | terminals (basically a typewriter machine connected with the 121 | computer using a serial port). ASCII defines DELETE as 7F, 122 | because, in punched-card terms, it means all the holes of the 123 | card punched; it is thus a kind of 'physical delete'. In the 124 | same way, the BACKSPACE key was a non-destructive backspace, 125 | as on a typewriter. So, if you wanted to delete a character, 126 | you had to BACKSPACE and then DELETE. Another use of BACKSPACE 127 | was to type accented characters, for example 'a BACKSPACE `'. 128 | The VT100 had no BACKSPACE key; it was generated using the 129 | CONTROL key as another control character (CONTROL key sets to 130 | 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code 131 | 0x08)), but it had a DELETE key in a similar position where 132 | the BACKSPACE key is located today on common PC keyboards. 133 | All the terminal emulators emulated the difference between 134 | these keys correctly: the backspace key generated a BACKSPACE 135 | (^H) and delete key generated a DELETE (^?). 136 | 137 | But a problem arose when Linus Torvalds wrote Linux. Unlike 138 | earlier terminals, the Linux virtual terminal (the terminal 139 | emulator integrated in the kernel) returned a DELETE when 140 | backspace was pressed, due to the VT100 having a DELETE key in 141 | the same position. This created a lot of problems (see [1] 142 | and [2]). Since Linux has become the king, a lot of terminal 143 | emulators today generate a DELETE when the backspace key is 144 | pressed in order to avoid problems with Linux. The result is 145 | that the only way of generating a BACKSPACE on these systems 146 | is by using CONTROL + H. (I also think that emacs had an 147 | important point here because the CONTROL + H prefix is used 148 | in emacs in some commands (help commands).) 149 | 150 | From point of view of the kernel, you can change the key 151 | for deleting a previous character with stty erase. When you 152 | connect a real terminal into a machine you describe the type 153 | of terminal, so getty configures the correct value of stty 154 | erase for this terminal. In the case of terminal emulators, 155 | however, you don't have any getty that can set the correct 156 | value of stty erase, so you always get the default value. 157 | For this reason, it is necessary to add 'stty erase ^H' to your 158 | profile if you have changed the value of the backspace key. 159 | Of course, another solution is for st itself to modify the 160 | value of stty erase. I usually have the inverse problem: 161 | when I connect to non-Unix machines, I have to press CONTROL + 162 | h to get a BACKSPACE. The inverse problem occurs when a user 163 | connects to my Unix machines from a different system with a 164 | correct backspace key. 165 | 166 | [1] http://www.ibb.net/~anne/keyboard.html 167 | [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html 168 | 169 | 170 | ## But I really want the old grumpy behaviour of my terminal 171 | 172 | Apply [1]. 173 | 174 | [1] https://st.suckless.org/patches/delkey 175 | 176 | 177 | ## Why do images not work in st using the w3m image hack? 178 | 179 | w3mimg uses a hack that draws an image on top of the terminal emulator Drawable 180 | window. The hack relies on the terminal to use a single buffer to draw its 181 | contents directly. 182 | 183 | st uses double-buffered drawing so the image is quickly replaced and may show a 184 | short flicker effect. 185 | 186 | Below is a patch example to change st double-buffering to a single Drawable 187 | buffer. 188 | 189 | diff --git a/x.c b/x.c 190 | --- a/x.c 191 | +++ b/x.c 192 | @@ -732,10 +732,6 @@ xresize(int col, int row) 193 | win.tw = col * win.cw; 194 | win.th = row * win.ch; 195 | 196 | - XFreePixmap(xw.dpy, xw.buf); 197 | - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 198 | - DefaultDepth(xw.dpy, xw.scr)); 199 | - XftDrawChange(xw.draw, xw.buf); 200 | xclear(0, 0, win.w, win.h); 201 | 202 | /* resize to new width */ 203 | @@ -1148,8 +1144,7 @@ xinit(int cols, int rows) 204 | gcvalues.graphics_exposures = False; 205 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 206 | &gcvalues); 207 | - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 208 | - DefaultDepth(xw.dpy, xw.scr)); 209 | + xw.buf = xw.win; 210 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 211 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 212 | 213 | @@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) 214 | void 215 | xfinishdraw(void) 216 | { 217 | - XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 218 | - win.h, 0, 0); 219 | XSetForeground(xw.dpy, dc.gc, 220 | dc.col[IS_SET(MODE_REVERSE)? 221 | defaultfg : defaultbg].pixel); 222 | 223 | 224 | ## BadLength X error in Xft when trying to render emoji 225 | 226 | Xft makes st crash when rendering color emojis with the following error: 227 | 228 | "X Error of failed request: BadLength (poly request too large or internal Xlib length error)" 229 | Major opcode of failed request: 139 (RENDER) 230 | Minor opcode of failed request: 20 (RenderAddGlyphs) 231 | Serial number of failed request: 1595 232 | Current serial number in output stream: 1818" 233 | 234 | This is a known bug in Xft (not st) which happens on some platforms and 235 | combination of particular fonts and fontconfig settings. 236 | 237 | See also: 238 | https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 239 | https://bugs.freedesktop.org/show_bug.cgi?id=107534 240 | https://bugzilla.redhat.com/show_bug.cgi?id=1498269 241 | 242 | The solution is to remove color emoji fonts or disable this in the fontconfig 243 | XML configuration. As an ugly workaround (which may work only on newer 244 | fontconfig versions (FC_COLOR)), the following code can be used to mask color 245 | fonts: 246 | 247 | FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); 248 | 249 | Please don't bother reporting this bug to st, but notify the upstream Xft 250 | developers about fixing this bug. 251 | 252 | As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5: 253 | https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS 254 | -------------------------------------------------------------------------------- /LEGACY: -------------------------------------------------------------------------------- 1 | A STATEMENT ON LEGACY SUPPORT 2 | 3 | In the terminal world there is much cruft that comes from old and unsup‐ 4 | ported terminals that inherit incompatible modes and escape sequences 5 | which noone is able to know, except when he/she comes from that time and 6 | developed a graphical vt100 emulator at that time. 7 | 8 | One goal of st is to only support what is really needed. When you en‐ 9 | counter a sequence which you really need, implement it. But while you 10 | are at it, do not add the other cruft you might encounter while sneek‐ 11 | ing at other terminal emulators. History has bloated them and there is 12 | no real evidence that most of the sequences are used today. 13 | 14 | 15 | Christoph Lohmann <20h@r-36.net> 16 | 2012-09-13T07:00:36.081271045+02:00 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2014-2022 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 | chmod 755 $(DESTDIR)$(PREFIX)/bin/st 47 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 48 | sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 49 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 50 | tic -sx st.info 51 | @echo Please see the README file regarding the terminfo entry of st. 52 | 53 | uninstall: 54 | rm -f $(DESTDIR)$(PREFIX)/bin/st 55 | rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 56 | 57 | .PHONY: all options clean dist install uninstall 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # st - simple terminal 2 | 3 | The [suckless terminal](https://st.suckless.org) (st) with some additional features. 4 | 5 | ## Unique features 6 | 7 | * color emoji support (ligature not support) 8 | * XIM over-the-spot support 9 | 10 | ## Other st patches 11 | 12 | * scrollback 13 | * vertcenter 14 | * xresources 15 | * externalpipe 16 | 17 | ## Requirements 18 | 19 | * Xlib 20 | * cairo 21 | * FontConfig 22 | * FreeType2 23 | 24 | ## Installation 25 | 26 | Edit config.mk to match your local setup (st is installed into the /usr/local namespace by default). 27 | 28 | Afterwards enter the following command to build and install st (if necessary as root): 29 | 30 | ```sh 31 | make clean install 32 | ``` 33 | 34 | ## Running st 35 | 36 | If you did not install st with make clean install, you must compile the st terminfo entry with the following command: 37 | 38 | ```sh 39 | tic -sx st.info 40 | ``` 41 | 42 | See the man page for additional details. 43 | 44 | ## Credits 45 | 46 | Based on Aurélien APTEL bt source code. 47 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | vt emulation 2 | ------------ 3 | 4 | * double-height support 5 | 6 | code & interface 7 | ---------------- 8 | 9 | * add a simple way to do multiplexing 10 | 11 | drawing 12 | ------- 13 | * add diacritics support to xdraws() 14 | * switch to a suckless font drawing library 15 | * make the font cache simpler 16 | * add better support for brightening of the upper colors 17 | 18 | bugs 19 | ---- 20 | 21 | * fix shift up/down (shift selection in emacs) 22 | * remove DEC test sequence when appropriate 23 | 24 | misc 25 | ---- 26 | 27 | $ grep -nE 'XXX|TODO' st.c 28 | 29 | -------------------------------------------------------------------------------- /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.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | /* 4 | * appearance 5 | * 6 | * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html 7 | */ 8 | static char *font = "Liberation Mono:pixelsize=12: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: scroll and/or utmp 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 | /* scroll program: to enable use a string like "scroll" */ 22 | char *scroll = NULL; 23 | char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 24 | 25 | /* identification sequence returned in DA and DECID */ 26 | char *vtiden = "\033[?6c"; 27 | 28 | /* Kerning / character bounding-box multipliers */ 29 | static float cwscale = 1.0; 30 | static float chscale = 1.0; 31 | 32 | /* 33 | * word delimiter string 34 | * 35 | * More advanced example: L" `'\"()[]{}" 36 | */ 37 | wchar_t *worddelimiters = L" "; 38 | 39 | /* selection timeouts (in milliseconds) */ 40 | static unsigned int doubleclicktimeout = 300; 41 | static unsigned int tripleclicktimeout = 600; 42 | 43 | /* alt screens */ 44 | int allowaltscreen = 1; 45 | 46 | /* allow certain non-interactive (insecure) window operations such as: 47 | setting the clipboard text */ 48 | int allowwindowops = 0; 49 | 50 | /* 51 | * draw latency range in ms - from new content/keypress/etc until drawing. 52 | * within this range, st draws when content stops arriving (idle). mostly it's 53 | * near minlatency, but it waits longer for slow updates to avoid partial draw. 54 | * low minlatency will tear/flicker more, as it can "detect" idle too early. 55 | */ 56 | static double minlatency = 8; 57 | static double maxlatency = 33; 58 | 59 | /* 60 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 61 | * attribute. 62 | */ 63 | static unsigned int blinktimeout = 800; 64 | 65 | /* 66 | * thickness of underline and bar cursors 67 | */ 68 | static unsigned int cursorthickness = 2; 69 | 70 | /* 71 | * bell volume. It must be a value between -100 and 100. Use 0 for disabling 72 | * it 73 | */ 74 | static int bellvolume = 0; 75 | 76 | /* default TERM value */ 77 | char *termname = "st-256color"; 78 | 79 | /* 80 | * spaces per tab 81 | * 82 | * When you are changing this value, don't forget to adapt the »it« value in 83 | * the st.info and appropriately install the st.info in the environment where 84 | * you use this st version. 85 | * 86 | * it#$tabspaces, 87 | * 88 | * Secondly make sure your kernel is not expanding tabs. When running `stty 89 | * -a` »tab0« should appear. You can tell the terminal to not expand tabs by 90 | * running following command: 91 | * 92 | * stty tabs 93 | */ 94 | unsigned int tabspaces = 8; 95 | 96 | /* Terminal colors (16 first used in escape sequence) */ 97 | static const char *colorname[] = { 98 | /* 8 normal colors */ 99 | "black", 100 | "red3", 101 | "green3", 102 | "yellow3", 103 | "blue2", 104 | "magenta3", 105 | "cyan3", 106 | "gray90", 107 | 108 | /* 8 bright colors */ 109 | "gray50", 110 | "red", 111 | "green", 112 | "yellow", 113 | "#5c5cff", 114 | "magenta", 115 | "cyan", 116 | "white", 117 | 118 | [255] = 0, 119 | 120 | /* more colors can be added after 255 to use with DefaultXX */ 121 | "#cccccc", 122 | "#555555", 123 | "gray90", /* default foreground colour */ 124 | "black", /* default background colour */ 125 | }; 126 | 127 | 128 | /* 129 | * Default colors (colorname index) 130 | * foreground, background, cursor, reverse cursor 131 | */ 132 | unsigned int defaultfg = 258; 133 | unsigned int defaultbg = 259; 134 | unsigned int defaultcs = 256; 135 | static unsigned int defaultrcs = 257; 136 | 137 | /* 138 | * Default shape of cursor 139 | * 2: Block ("█") 140 | * 4: Underline ("_") 141 | * 6: Bar ("|") 142 | * 7: Snowman ("☃") 143 | */ 144 | static unsigned int cursorshape = 2; 145 | 146 | /* 147 | * Default columns and rows numbers 148 | */ 149 | 150 | static unsigned int cols = 80; 151 | static unsigned int rows = 24; 152 | 153 | /* 154 | * Default colour and shape of the mouse cursor 155 | */ 156 | static unsigned int mouseshape = XC_xterm; 157 | static unsigned int mousefg = 7; 158 | static unsigned int mousebg = 0; 159 | 160 | /* 161 | * Color used to display font attributes when fontconfig selected a font which 162 | * doesn't match the ones requested. 163 | */ 164 | static unsigned int defaultattr = 11; 165 | 166 | /* 167 | * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 168 | * Note that if you want to use ShiftMask with selmasks, set this to an other 169 | * modifier, set to 0 to not use it. 170 | */ 171 | static uint forcemousemod = ShiftMask; 172 | 173 | /* 174 | * XIM preedit style 175 | */ 176 | static char *imstyle = "root"; 177 | 178 | /* 179 | * Xresources preferences to load at startup 180 | */ 181 | ResourcePref resources[] = { 182 | { "font", STRING, &font }, 183 | { "color0", STRING, &colorname[0] }, 184 | { "color1", STRING, &colorname[1] }, 185 | { "color2", STRING, &colorname[2] }, 186 | { "color3", STRING, &colorname[3] }, 187 | { "color4", STRING, &colorname[4] }, 188 | { "color5", STRING, &colorname[5] }, 189 | { "color6", STRING, &colorname[6] }, 190 | { "color7", STRING, &colorname[7] }, 191 | { "color8", STRING, &colorname[8] }, 192 | { "color9", STRING, &colorname[9] }, 193 | { "color10", STRING, &colorname[10] }, 194 | { "color11", STRING, &colorname[11] }, 195 | { "color12", STRING, &colorname[12] }, 196 | { "color13", STRING, &colorname[13] }, 197 | { "color14", STRING, &colorname[14] }, 198 | { "color15", STRING, &colorname[15] }, 199 | { "cursorbg", STRING, &colorname[256] }, 200 | { "cursorfg", STRING, &colorname[257] }, 201 | { "termname", STRING, &termname }, 202 | { "shell", STRING, &shell }, 203 | { "minlatency", INTEGER, &minlatency }, 204 | { "maxlatency", INTEGER, &maxlatency }, 205 | { "blinktimeout", INTEGER, &blinktimeout }, 206 | { "bellvolume", INTEGER, &bellvolume }, 207 | { "tabspaces", INTEGER, &tabspaces }, 208 | { "borderpx", INTEGER, &borderpx }, 209 | { "cwscale", FLOAT, &cwscale }, 210 | { "chscale", FLOAT, &chscale }, 211 | }; 212 | 213 | /* 214 | * Internal mouse shortcuts. 215 | * Beware that overloading Button1 will disable the selection. 216 | */ 217 | static MouseShortcut mshortcuts[] = { 218 | /* mask button function argument release */ 219 | { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 220 | { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, 221 | { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 222 | { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, 223 | { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 224 | }; 225 | 226 | 227 | /* 228 | * Open url command 229 | */ 230 | static char *openurlcmd[] = { "/bin/sh", "-c", 231 | "xurls | dmenu -fn monospace:size=10 -b -p 'xdg-open:' -i -nb '#101010' | xargs -r xdg-open", 232 | "externalpipe", NULL }; 233 | 234 | /* Internal keyboard shortcuts. */ 235 | #define MODKEY Mod1Mask 236 | #define TERMMOD (ControlMask|ShiftMask) 237 | 238 | static Shortcut shortcuts[] = { 239 | /* mask keysym function argument */ 240 | { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 241 | { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 242 | { ShiftMask, XK_Print, printscreen, {.i = 0} }, 243 | { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, 244 | { TERMMOD, XK_Prior, zoom, {.f = +1} }, 245 | { TERMMOD, XK_Next, zoom, {.f = -1} }, 246 | { TERMMOD, XK_Home, zoomreset, {.f = 0} }, 247 | { TERMMOD, XK_C, clipcopy, {.i = 0} }, 248 | { TERMMOD, XK_V, clippaste, {.i = 0} }, 249 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 250 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 251 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 252 | { TERMMOD, XK_I, iso14755, {.i = 0} }, 253 | { TERMMOD, XK_U, externalpipe, {.v = openurlcmd } }, 254 | { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 255 | { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 256 | }; 257 | 258 | /* 259 | * Special keys (change & recompile st.info accordingly) 260 | * 261 | * Mask value: 262 | * * Use XK_ANY_MOD to match the key no matter modifiers state 263 | * * Use XK_NO_MOD to match the key alone (no modifiers) 264 | * appkey value: 265 | * * 0: no value 266 | * * > 0: keypad application mode enabled 267 | * * = 2: term.numlock = 1 268 | * * < 0: keypad application mode disabled 269 | * appcursor value: 270 | * * 0: no value 271 | * * > 0: cursor application mode enabled 272 | * * < 0: cursor application mode disabled 273 | * 274 | * Be careful with the order of the definitions because st searches in 275 | * this table sequentially, so any XK_ANY_MOD must be in the last 276 | * position for a key. 277 | */ 278 | 279 | /* 280 | * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) 281 | * to be mapped below, add them to this array. 282 | */ 283 | static KeySym mappedkeys[] = { -1 }; 284 | 285 | /* 286 | * State bits to ignore when matching key or button events. By default, 287 | * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. 288 | */ 289 | static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; 290 | 291 | /* 292 | * This is the huge key array which defines all compatibility to the Linux 293 | * world. Please decide about changes wisely. 294 | */ 295 | static Key key[] = { 296 | /* keysym mask string appkey appcursor */ 297 | { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, 298 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, 299 | { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, 300 | { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 301 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, 302 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, 303 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, 304 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, 305 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, 306 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, 307 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, 308 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, 309 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, 310 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, 311 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, 312 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, 313 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, 314 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 315 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, 316 | { XK_KP_End, ControlMask, "\033[J", -1, 0}, 317 | { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, 318 | { XK_KP_End, ShiftMask, "\033[K", -1, 0}, 319 | { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, 320 | { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, 321 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, 322 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 323 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, 324 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, 325 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, 326 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, 327 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 328 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 329 | { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, 330 | { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, 331 | { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, 332 | { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, 333 | { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 334 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 335 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, 336 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, 337 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, 338 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, 339 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, 340 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, 341 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, 342 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, 343 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, 344 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, 345 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, 346 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, 347 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, 348 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, 349 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, 350 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, 351 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, 352 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, 353 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, 354 | { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, 355 | { XK_Up, ControlMask, "\033[1;5A", 0, 0}, 356 | { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, 357 | { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, 358 | { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, 359 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, 360 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, 361 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, 362 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, 363 | { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, 364 | { XK_Down, ControlMask, "\033[1;5B", 0, 0}, 365 | { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, 366 | { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, 367 | { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, 368 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, 369 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, 370 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, 371 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, 372 | { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, 373 | { XK_Left, ControlMask, "\033[1;5D", 0, 0}, 374 | { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, 375 | { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, 376 | { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, 377 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, 378 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, 379 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, 380 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, 381 | { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, 382 | { XK_Right, ControlMask, "\033[1;5C", 0, 0}, 383 | { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, 384 | { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, 385 | { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, 386 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, 387 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, 388 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, 389 | { XK_Return, Mod1Mask, "\033\r", 0, 0}, 390 | { XK_Return, XK_ANY_MOD, "\r", 0, 0}, 391 | { XK_Insert, ShiftMask, "\033[4l", -1, 0}, 392 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, 393 | { XK_Insert, ControlMask, "\033[L", -1, 0}, 394 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, 395 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 396 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 397 | { XK_Delete, ControlMask, "\033[M", -1, 0}, 398 | { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, 399 | { XK_Delete, ShiftMask, "\033[2K", -1, 0}, 400 | { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, 401 | { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 402 | { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 403 | { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, 404 | { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, 405 | { XK_Home, ShiftMask, "\033[2J", 0, -1}, 406 | { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, 407 | { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, 408 | { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 409 | { XK_End, ControlMask, "\033[J", -1, 0}, 410 | { XK_End, ControlMask, "\033[1;5F", +1, 0}, 411 | { XK_End, ShiftMask, "\033[K", -1, 0}, 412 | { XK_End, ShiftMask, "\033[1;2F", +1, 0}, 413 | { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, 414 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, 415 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, 416 | { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 417 | { XK_Next, ControlMask, "\033[6;5~", 0, 0}, 418 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, 419 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 420 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, 421 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, 422 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, 423 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, 424 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, 425 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, 426 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, 427 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, 428 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, 429 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, 430 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, 431 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, 432 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, 433 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, 434 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, 435 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, 436 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, 437 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, 438 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, 439 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, 440 | { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, 441 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, 442 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, 443 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, 444 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, 445 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, 446 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, 447 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, 448 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, 449 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, 450 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, 451 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, 452 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, 453 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, 454 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, 455 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, 456 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, 457 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, 458 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, 459 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, 460 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, 461 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, 462 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, 463 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, 464 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, 465 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, 466 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, 467 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, 468 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, 469 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, 470 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, 471 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, 472 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, 473 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, 474 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, 475 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, 476 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, 477 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, 478 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, 479 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, 480 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, 481 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, 482 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, 483 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, 484 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, 485 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, 486 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, 487 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, 488 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, 489 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, 490 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, 491 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, 492 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, 493 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, 494 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, 495 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, 496 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, 497 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, 498 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, 499 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, 500 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, 501 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, 502 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, 503 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, 504 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, 505 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, 506 | }; 507 | 508 | /* 509 | * Selection types' masks. 510 | * Use the same masks as usual. 511 | * Button1Mask is always unset, to make masks match between ButtonPress. 512 | * ButtonRelease and MotionNotify. 513 | * If no match is found, regular selection is used. 514 | */ 515 | static uint selmasks[] = { 516 | [SEL_RECTANGULAR] = Mod1Mask, 517 | }; 518 | 519 | /* 520 | * Printable characters in ASCII, used to estimate the advance width 521 | * of single wide characters. 522 | */ 523 | static char ascii_printable[] = 524 | " !\"#$%&'()*+,-./0123456789:;<=>?" 525 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 526 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 527 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # st version 2 | VERSION = 0.9 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 | `$(PKG_CONFIG) --cflags cairo` 20 | LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil \ 21 | `$(PKG_CONFIG) --libs fontconfig` \ 22 | `$(PKG_CONFIG) --libs freetype2` \ 23 | `$(PKG_CONFIG) --libs cairo` 24 | 25 | # flags 26 | STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 27 | STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) 28 | STLDFLAGS = $(LIBS) $(LDFLAGS) 29 | 30 | # OpenBSD: 31 | #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE 32 | #LIBS = -L$(X11LIB) -lm -lX11 -lutil \ 33 | # `$(PKG_CONFIG) --libs fontconfig` \ 34 | # `$(PKG_CONFIG) --libs freetype2` \ 35 | # `$(PKG_CONFIG) --libs cairo` 36 | #MANPREFIX = ${PREFIX}/man 37 | 38 | # compiler and linker 39 | # CC = c99 40 | -------------------------------------------------------------------------------- /st.1: -------------------------------------------------------------------------------- 1 | .TH ST 1 st\-VERSION 2 | .SH NAME 3 | st \- simple terminal 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 Break 129 | Send a break in the serial line. 130 | Break key is obtained in PC keyboards 131 | pressing at the same time control and pause. 132 | .TP 133 | .B Ctrl-Print Screen 134 | Toggle if st should print to the 135 | .I iofile. 136 | .TP 137 | .B Shift-Print Screen 138 | Print the full screen to the 139 | .I iofile. 140 | .TP 141 | .B Print Screen 142 | Print the selection to the 143 | .I iofile. 144 | .TP 145 | .B Ctrl-Shift-Page Up 146 | Increase font size. 147 | .TP 148 | .B Ctrl-Shift-Page Down 149 | Decrease font size. 150 | .TP 151 | .B Ctrl-Shift-Home 152 | Reset to default font size. 153 | .TP 154 | .B Ctrl-Shift-y 155 | Paste from primary selection (middle mouse button). 156 | .TP 157 | .B Ctrl-Shift-c 158 | Copy the selected text to the clipboard selection. 159 | .TP 160 | .B Ctrl-Shift-v 161 | Paste from the clipboard selection. 162 | .SH CUSTOMIZATION 163 | .B st 164 | can be customized by creating a custom config.h and (re)compiling the source 165 | code. This keeps it fast, secure and simple. 166 | .SH AUTHORS 167 | See the LICENSE file for the authors. 168 | .SH LICENSE 169 | See the LICENSE file for the terms of redistribution. 170 | .SH SEE ALSO 171 | .BR tabbed (1), 172 | .BR utmp (1), 173 | .BR stty (1), 174 | .BR scroll (1) 175 | .SH BUGS 176 | See the TODO file in the distribution. 177 | 178 | -------------------------------------------------------------------------------- /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 | 39 | /* macros */ 40 | #define IS_SET(flag) ((term.mode & (flag)) != 0) 41 | #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 42 | #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 43 | #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 44 | #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 45 | 46 | /* constants */ 47 | #define ISO14755CMD "dmenu -fn monospace:size=10 -b -i -nb '#101010' -p 'codepoint:' ] [;]] []] */ 141 | typedef struct { 142 | char buf[ESC_BUF_SIZ]; /* raw string */ 143 | size_t len; /* raw string length */ 144 | char priv; 145 | int arg[ESC_ARG_SIZ]; 146 | int narg; /* nb of args */ 147 | char mode[2]; 148 | } CSIEscape; 149 | 150 | /* STR Escape sequence structs */ 151 | /* ESC type [[ [] [;]] ] ESC '\' */ 152 | typedef struct { 153 | char type; /* ESC type ... */ 154 | char *buf; /* allocated raw string */ 155 | size_t siz; /* allocation size */ 156 | size_t len; /* raw string length */ 157 | char *args[STR_ARG_SIZ]; 158 | int narg; /* nb of args */ 159 | } STREscape; 160 | 161 | static void execsh(char *, char **); 162 | static void stty(char **); 163 | static void sigchld(int); 164 | static void ttywriteraw(const char *, size_t); 165 | static void cleanup(); 166 | 167 | static void csidump(void); 168 | static void csihandle(void); 169 | static void csiparse(void); 170 | static void csireset(void); 171 | static void osc_color_response(int, int, int); 172 | static int eschandle(uchar); 173 | static void strdump(void); 174 | static void strhandle(void); 175 | static void strparse(void); 176 | static void strreset(void); 177 | 178 | static void tprinter(char *, size_t); 179 | static void tdumpsel(void); 180 | static void tdumpline(int); 181 | static void tdump(void); 182 | static void tclearregion(int, int, int, int); 183 | static void tcursor(int); 184 | static void tdeletechar(int); 185 | static void tdeleteline(int); 186 | static void tinsertblank(int); 187 | static void tinsertblankline(int); 188 | static int tlinelen(int); 189 | static void tmoveto(int, int); 190 | static void tmoveato(int, int); 191 | static void tnewline(int); 192 | static void tputtab(int); 193 | static void tputc(Rune); 194 | static void treset(void); 195 | static void tscrollup(int, int, int); 196 | static void tscrolldown(int, int, int); 197 | static void tsetattr(const int *, int); 198 | static void tsetchar(Rune, const Glyph *, int, int); 199 | static void tsetdirt(int, int); 200 | static void tsetscroll(int, int); 201 | static void tswapscreen(void); 202 | static void tsetmode(int, int, const int *, int); 203 | static int twrite(const char *, int, int); 204 | static void tfulldirt(void); 205 | static void tcontrolcode(uchar ); 206 | static void tdectest(char ); 207 | static void tdefutf8(char); 208 | static int32_t tdefcolor(const int *, int *, int); 209 | static void tdeftran(char); 210 | static void tstrsequence(uchar); 211 | 212 | static void drawregion(int, int, int, int); 213 | 214 | static void selnormalize(void); 215 | static void selscroll(int, int); 216 | static void selsnap(int *, int *, int); 217 | 218 | static size_t utf8decode(const char *, Rune *, size_t); 219 | static Rune utf8decodebyte(char, size_t *); 220 | static char utf8encodebyte(Rune, size_t); 221 | static size_t utf8validate(Rune *, size_t); 222 | 223 | static char *base64dec(const char *); 224 | static char base64dec_getc(const char **); 225 | 226 | static ssize_t xwrite(int, const char *, size_t); 227 | 228 | /* Globals */ 229 | static Term term; 230 | static Selection sel; 231 | static CSIEscape csiescseq; 232 | static STREscape strescseq; 233 | static int iofd = 1; 234 | static int cmdfd; 235 | static pid_t pid; 236 | static CleanFunc *cleanfuncs = NULL; 237 | static int ncleanfunc = 0; 238 | 239 | static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 240 | static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 241 | static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 242 | static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 243 | 244 | ssize_t 245 | xwrite(int fd, const char *s, size_t len) 246 | { 247 | size_t aux = len; 248 | ssize_t r; 249 | 250 | while (len > 0) { 251 | r = write(fd, s, len); 252 | if (r < 0) 253 | return r; 254 | len -= r; 255 | s += r; 256 | } 257 | 258 | return aux; 259 | } 260 | 261 | void * 262 | xmalloc(size_t len) 263 | { 264 | void *p; 265 | 266 | if (!(p = malloc(len))) 267 | die("malloc: %s\n", strerror(errno)); 268 | 269 | return p; 270 | } 271 | 272 | void * 273 | xrealloc(void *p, size_t len) 274 | { 275 | if ((p = realloc(p, len)) == NULL) 276 | die("realloc: %s\n", strerror(errno)); 277 | 278 | return p; 279 | } 280 | 281 | char * 282 | xstrdup(const char *s) 283 | { 284 | char *p; 285 | 286 | if ((p = strdup(s)) == NULL) 287 | die("strdup: %s\n", strerror(errno)); 288 | 289 | return p; 290 | } 291 | 292 | size_t 293 | utf8decode(const char *c, Rune *u, size_t clen) 294 | { 295 | size_t i, j, len, type; 296 | Rune udecoded; 297 | 298 | *u = UTF_INVALID; 299 | if (!clen) 300 | return 0; 301 | udecoded = utf8decodebyte(c[0], &len); 302 | if (!BETWEEN(len, 1, UTF_SIZ)) 303 | return 1; 304 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 305 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 306 | if (type != 0) 307 | return j; 308 | } 309 | if (j < len) 310 | return 0; 311 | *u = udecoded; 312 | utf8validate(u, len); 313 | 314 | return len; 315 | } 316 | 317 | Rune 318 | utf8decodebyte(char c, size_t *i) 319 | { 320 | for (*i = 0; *i < LEN(utfmask); ++(*i)) 321 | if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 322 | return (uchar)c & ~utfmask[*i]; 323 | 324 | return 0; 325 | } 326 | 327 | size_t 328 | utf8encode(Rune u, char *c) 329 | { 330 | size_t len, i; 331 | 332 | len = utf8validate(&u, 0); 333 | if (len > UTF_SIZ) 334 | return 0; 335 | 336 | for (i = len - 1; i != 0; --i) { 337 | c[i] = utf8encodebyte(u, 0); 338 | u >>= 6; 339 | } 340 | c[0] = utf8encodebyte(u, len); 341 | 342 | return len; 343 | } 344 | 345 | char 346 | utf8encodebyte(Rune u, size_t i) 347 | { 348 | return utfbyte[i] | (u & ~utfmask[i]); 349 | } 350 | 351 | size_t 352 | utf8validate(Rune *u, size_t i) 353 | { 354 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 355 | *u = UTF_INVALID; 356 | for (i = 1; *u > utfmax[i]; ++i) 357 | ; 358 | 359 | return i; 360 | } 361 | 362 | char 363 | base64dec_getc(const char **src) 364 | { 365 | while (**src && !isprint((unsigned char)**src)) 366 | (*src)++; 367 | return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 368 | } 369 | 370 | char * 371 | base64dec(const char *src) 372 | { 373 | size_t in_len = strlen(src); 374 | char *result, *dst; 375 | static const char base64_digits[256] = { 376 | [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 377 | 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 378 | 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 379 | 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 380 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 381 | }; 382 | 383 | if (in_len % 4) 384 | in_len += 4 - (in_len % 4); 385 | result = dst = xmalloc(in_len / 4 * 3 + 1); 386 | while (*src) { 387 | int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 388 | int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 389 | int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 390 | int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 391 | 392 | /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 393 | if (a == -1 || b == -1) 394 | break; 395 | 396 | *dst++ = (a << 2) | ((b & 0x30) >> 4); 397 | if (c == -1) 398 | break; 399 | *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 400 | if (d == -1) 401 | break; 402 | *dst++ = ((c & 0x03) << 6) | d; 403 | } 404 | *dst = '\0'; 405 | return result; 406 | } 407 | 408 | void 409 | selinit(void) 410 | { 411 | sel.mode = SEL_IDLE; 412 | sel.snap = 0; 413 | sel.ob.x = -1; 414 | } 415 | 416 | int 417 | tlinelen(int y) 418 | { 419 | int i = term.col; 420 | 421 | if (TLINE(y)[i - 1].mode & ATTR_WRAP) 422 | return i; 423 | 424 | while (i > 0 && TLINE(y)[i - 1].u == ' ') 425 | --i; 426 | 427 | return i; 428 | } 429 | 430 | void 431 | selstart(int col, int row, int snap) 432 | { 433 | selclear(); 434 | sel.mode = SEL_EMPTY; 435 | sel.type = SEL_REGULAR; 436 | sel.alt = IS_SET(MODE_ALTSCREEN); 437 | sel.snap = snap; 438 | sel.oe.x = sel.ob.x = col; 439 | sel.oe.y = sel.ob.y = row; 440 | selnormalize(); 441 | 442 | if (sel.snap != 0) 443 | sel.mode = SEL_READY; 444 | tsetdirt(sel.nb.y, sel.ne.y); 445 | } 446 | 447 | void 448 | selextend(int col, int row, int type, int done) 449 | { 450 | int oldey, oldex, oldsby, oldsey, oldtype; 451 | 452 | if (sel.mode == SEL_IDLE) 453 | return; 454 | if (done && sel.mode == SEL_EMPTY) { 455 | selclear(); 456 | return; 457 | } 458 | 459 | oldey = sel.oe.y; 460 | oldex = sel.oe.x; 461 | oldsby = sel.nb.y; 462 | oldsey = sel.ne.y; 463 | oldtype = sel.type; 464 | 465 | sel.oe.x = col; 466 | sel.oe.y = row; 467 | selnormalize(); 468 | sel.type = type; 469 | 470 | if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 471 | tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 472 | 473 | sel.mode = done ? SEL_IDLE : SEL_READY; 474 | } 475 | 476 | void 477 | selnormalize(void) 478 | { 479 | int i; 480 | 481 | if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 482 | sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 483 | sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 484 | } else { 485 | sel.nb.x = MIN(sel.ob.x, sel.oe.x); 486 | sel.ne.x = MAX(sel.ob.x, sel.oe.x); 487 | } 488 | sel.nb.y = MIN(sel.ob.y, sel.oe.y); 489 | sel.ne.y = MAX(sel.ob.y, sel.oe.y); 490 | 491 | selsnap(&sel.nb.x, &sel.nb.y, -1); 492 | selsnap(&sel.ne.x, &sel.ne.y, +1); 493 | 494 | /* expand selection over line breaks */ 495 | if (sel.type == SEL_RECTANGULAR) 496 | return; 497 | i = tlinelen(sel.nb.y); 498 | if (i < sel.nb.x) 499 | sel.nb.x = i; 500 | if (tlinelen(sel.ne.y) <= sel.ne.x) 501 | sel.ne.x = term.col - 1; 502 | } 503 | 504 | int 505 | selected(int x, int y) 506 | { 507 | if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 508 | sel.alt != IS_SET(MODE_ALTSCREEN)) 509 | return 0; 510 | 511 | if (sel.type == SEL_RECTANGULAR) 512 | return BETWEEN(y, sel.nb.y, sel.ne.y) 513 | && BETWEEN(x, sel.nb.x, sel.ne.x); 514 | 515 | return BETWEEN(y, sel.nb.y, sel.ne.y) 516 | && (y != sel.nb.y || x >= sel.nb.x) 517 | && (y != sel.ne.y || x <= sel.ne.x); 518 | } 519 | 520 | void 521 | selsnap(int *x, int *y, int direction) 522 | { 523 | int newx, newy, xt, yt; 524 | int delim, prevdelim; 525 | const Glyph *gp, *prevgp; 526 | 527 | switch (sel.snap) { 528 | case SNAP_WORD: 529 | /* 530 | * Snap around if the word wraps around at the end or 531 | * beginning of a line. 532 | */ 533 | prevgp = &TLINE(*y)[*x]; 534 | prevdelim = ISDELIM(prevgp->u); 535 | for (;;) { 536 | newx = *x + direction; 537 | newy = *y; 538 | if (!BETWEEN(newx, 0, term.col - 1)) { 539 | newy += direction; 540 | newx = (newx + term.col) % term.col; 541 | if (!BETWEEN(newy, 0, term.row - 1)) 542 | break; 543 | 544 | if (direction > 0) 545 | yt = *y, xt = *x; 546 | else 547 | yt = newy, xt = newx; 548 | if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 549 | break; 550 | } 551 | 552 | if (newx >= tlinelen(newy)) 553 | break; 554 | 555 | gp = &TLINE(newy)[newx]; 556 | delim = ISDELIM(gp->u); 557 | if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 558 | || (delim && gp->u != prevgp->u))) 559 | break; 560 | 561 | *x = newx; 562 | *y = newy; 563 | prevgp = gp; 564 | prevdelim = delim; 565 | } 566 | break; 567 | case SNAP_LINE: 568 | /* 569 | * Snap around if the the previous line or the current one 570 | * has set ATTR_WRAP at its end. Then the whole next or 571 | * previous line will be selected. 572 | */ 573 | *x = (direction < 0) ? 0 : term.col - 1; 574 | if (direction < 0) { 575 | for (; *y > 0; *y += direction) { 576 | if (!(TLINE(*y-1)[term.col-1].mode 577 | & ATTR_WRAP)) { 578 | break; 579 | } 580 | } 581 | } else if (direction > 0) { 582 | for (; *y < term.row-1; *y += direction) { 583 | if (!(TLINE(*y)[term.col-1].mode 584 | & ATTR_WRAP)) { 585 | break; 586 | } 587 | } 588 | } 589 | break; 590 | } 591 | } 592 | 593 | char * 594 | getsel(void) 595 | { 596 | char *str, *ptr; 597 | int y, bufsize, lastx, linelen; 598 | const Glyph *gp, *last; 599 | 600 | if (sel.ob.x == -1) 601 | return NULL; 602 | 603 | bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 604 | ptr = str = xmalloc(bufsize); 605 | 606 | /* append every set & selected glyph to the selection */ 607 | for (y = sel.nb.y; y <= sel.ne.y; y++) { 608 | if ((linelen = tlinelen(y)) == 0) { 609 | *ptr++ = '\n'; 610 | continue; 611 | } 612 | 613 | if (sel.type == SEL_RECTANGULAR) { 614 | gp = &TLINE(y)[sel.nb.x]; 615 | lastx = sel.ne.x; 616 | } else { 617 | gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 618 | lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 619 | } 620 | last = &TLINE(y)[MIN(lastx, linelen-1)]; 621 | while (last >= gp && last->u == ' ') 622 | --last; 623 | 624 | for ( ; gp <= last; ++gp) { 625 | if (gp->mode & ATTR_WDUMMY) 626 | continue; 627 | 628 | ptr += utf8encode(gp->u, ptr); 629 | } 630 | 631 | /* 632 | * Copy and pasting of line endings is inconsistent 633 | * in the inconsistent terminal and GUI world. 634 | * The best solution seems like to produce '\n' when 635 | * something is copied from st and convert '\n' to 636 | * '\r', when something to be pasted is received by 637 | * st. 638 | * FIXME: Fix the computer world. 639 | */ 640 | if ((y < sel.ne.y || lastx >= linelen) && 641 | (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 642 | *ptr++ = '\n'; 643 | } 644 | *ptr = 0; 645 | return str; 646 | } 647 | 648 | void 649 | selclear(void) 650 | { 651 | if (sel.ob.x == -1) 652 | return; 653 | sel.mode = SEL_IDLE; 654 | sel.ob.x = -1; 655 | tsetdirt(sel.nb.y, sel.ne.y); 656 | } 657 | 658 | void 659 | die(const char *errstr, ...) 660 | { 661 | va_list ap; 662 | 663 | va_start(ap, errstr); 664 | vfprintf(stderr, errstr, ap); 665 | va_end(ap); 666 | exit(1); 667 | } 668 | 669 | void 670 | execsh(char *cmd, char **args) 671 | { 672 | char *sh, *prog, *arg; 673 | const struct passwd *pw; 674 | 675 | errno = 0; 676 | if ((pw = getpwuid(getuid())) == NULL) { 677 | if (errno) 678 | die("getpwuid: %s\n", strerror(errno)); 679 | else 680 | die("who are you?\n"); 681 | } 682 | 683 | if ((sh = getenv("SHELL")) == NULL) 684 | sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 685 | 686 | if (args) { 687 | prog = args[0]; 688 | arg = NULL; 689 | } else if (scroll) { 690 | prog = scroll; 691 | arg = utmp ? utmp : sh; 692 | } else if (utmp) { 693 | prog = utmp; 694 | arg = NULL; 695 | } else { 696 | prog = sh; 697 | arg = NULL; 698 | } 699 | DEFAULT(args, ((char *[]) {prog, arg, NULL})); 700 | 701 | unsetenv("COLUMNS"); 702 | unsetenv("LINES"); 703 | unsetenv("TERMCAP"); 704 | setenv("LOGNAME", pw->pw_name, 1); 705 | setenv("USER", pw->pw_name, 1); 706 | setenv("SHELL", sh, 1); 707 | setenv("HOME", pw->pw_dir, 1); 708 | setenv("TERM", termname, 1); 709 | 710 | signal(SIGCHLD, SIG_DFL); 711 | signal(SIGHUP, SIG_DFL); 712 | signal(SIGINT, SIG_DFL); 713 | signal(SIGQUIT, SIG_DFL); 714 | signal(SIGTERM, SIG_DFL); 715 | signal(SIGALRM, SIG_DFL); 716 | 717 | execvp(prog, args); 718 | _exit(1); 719 | } 720 | 721 | void 722 | addcleanfunc(CleanFunc func) 723 | { 724 | cleanfuncs = realloc(cleanfuncs, sizeof(CleanFunc) * (ncleanfunc + 1)); 725 | cleanfuncs[ncleanfunc++] = func; 726 | } 727 | 728 | void 729 | cleanup() 730 | { 731 | int i; 732 | close(cmdfd); 733 | for (i = 0; i < ncleanfunc; i++) 734 | cleanfuncs[i](); 735 | free(cleanfuncs); 736 | } 737 | 738 | void 739 | sigchld(int a) 740 | { 741 | int stat; 742 | pid_t p; 743 | 744 | if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 745 | die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 746 | 747 | if (pid != p) 748 | return; 749 | cleanup(); 750 | 751 | if (WIFEXITED(stat) && WEXITSTATUS(stat)) 752 | fprintf(stderr, "child exited with status %d\n", WEXITSTATUS(stat)); 753 | else if (WIFSIGNALED(stat)) 754 | die("child terminated due to signal %d\n", WTERMSIG(stat)); 755 | _exit(0); 756 | } 757 | 758 | void 759 | stty(char **args) 760 | { 761 | char cmd[_POSIX_ARG_MAX], **p, *q, *s; 762 | size_t n, siz; 763 | 764 | if ((n = strlen(stty_args)) > sizeof(cmd)-1) 765 | die("incorrect stty parameters\n"); 766 | memcpy(cmd, stty_args, n); 767 | q = cmd + n; 768 | siz = sizeof(cmd) - n; 769 | for (p = args; p && (s = *p); ++p) { 770 | if ((n = strlen(s)) > siz-1) 771 | die("stty parameter length too long\n"); 772 | *q++ = ' '; 773 | memcpy(q, s, n); 774 | q += n; 775 | siz -= n + 1; 776 | } 777 | *q = '\0'; 778 | if (system(cmd) != 0) 779 | perror("Couldn't call stty"); 780 | } 781 | 782 | int 783 | ttynew(const char *line, char *cmd, const char *out, char **args) 784 | { 785 | int m, s; 786 | 787 | if (out) { 788 | term.mode |= MODE_PRINT; 789 | iofd = (!strcmp(out, "-")) ? 790 | 1 : open(out, O_WRONLY | O_CREAT, 0666); 791 | if (iofd < 0) { 792 | fprintf(stderr, "Error opening %s:%s\n", 793 | out, strerror(errno)); 794 | } 795 | } 796 | 797 | if (line) { 798 | if ((cmdfd = open(line, O_RDWR)) < 0) 799 | die("open line '%s' failed: %s\n", 800 | line, strerror(errno)); 801 | dup2(cmdfd, 0); 802 | stty(args); 803 | return cmdfd; 804 | } 805 | 806 | /* seems to work fine on linux, openbsd and freebsd */ 807 | if (openpty(&m, &s, NULL, NULL, NULL) < 0) 808 | die("openpty failed: %s\n", strerror(errno)); 809 | 810 | switch (pid = fork()) { 811 | case -1: 812 | die("fork failed: %s\n", strerror(errno)); 813 | break; 814 | case 0: 815 | close(iofd); 816 | close(m); 817 | setsid(); /* create a new process group */ 818 | dup2(s, 0); 819 | dup2(s, 1); 820 | dup2(s, 2); 821 | if (ioctl(s, TIOCSCTTY, NULL) < 0) 822 | die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 823 | if (s > 2) 824 | close(s); 825 | #ifdef __OpenBSD__ 826 | if (pledge("stdio getpw proc exec", NULL) == -1) 827 | die("pledge\n"); 828 | #endif 829 | execsh(cmd, args); 830 | break; 831 | default: 832 | #ifdef __OpenBSD__ 833 | if (pledge("stdio rpath tty proc", NULL) == -1) 834 | die("pledge\n"); 835 | #endif 836 | close(s); 837 | cmdfd = m; 838 | signal(SIGCHLD, sigchld); 839 | break; 840 | } 841 | return cmdfd; 842 | } 843 | 844 | size_t 845 | ttyread(void) 846 | { 847 | static char buf[BUFSIZ]; 848 | static int buflen = 0; 849 | int ret, written; 850 | 851 | /* append read bytes to unprocessed bytes */ 852 | ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 853 | 854 | switch (ret) { 855 | case 0: 856 | exit(0); 857 | case -1: 858 | die("couldn't read from shell: %s\n", strerror(errno)); 859 | default: 860 | buflen += ret; 861 | written = twrite(buf, buflen, 0); 862 | buflen -= written; 863 | /* keep any incomplete UTF-8 byte sequence for the next call */ 864 | if (buflen > 0) 865 | memmove(buf, buf + written, buflen); 866 | if (term.scr > 0 && term.scr < HISTSIZE-1) 867 | term.scr++; 868 | return ret; 869 | } 870 | } 871 | 872 | void 873 | ttywrite(const char *s, size_t n, int may_echo) 874 | { 875 | const char *next; 876 | Arg arg = (Arg) { .i = term.scr }; 877 | 878 | kscrolldown(&arg); 879 | 880 | if (may_echo && IS_SET(MODE_ECHO)) 881 | twrite(s, n, 1); 882 | 883 | if (!IS_SET(MODE_CRLF)) { 884 | ttywriteraw(s, n); 885 | return; 886 | } 887 | 888 | /* This is similar to how the kernel handles ONLCR for ttys */ 889 | while (n > 0) { 890 | if (*s == '\r') { 891 | next = s + 1; 892 | ttywriteraw("\r\n", 2); 893 | } else { 894 | next = memchr(s, '\r', n); 895 | DEFAULT(next, s + n); 896 | ttywriteraw(s, next - s); 897 | } 898 | n -= next - s; 899 | s = next; 900 | } 901 | } 902 | 903 | void 904 | ttywriteraw(const char *s, size_t n) 905 | { 906 | fd_set wfd, rfd; 907 | ssize_t r; 908 | size_t lim = 256; 909 | 910 | /* 911 | * Remember that we are using a pty, which might be a modem line. 912 | * Writing too much will clog the line. That's why we are doing this 913 | * dance. 914 | * FIXME: Migrate the world to Plan 9. 915 | */ 916 | while (n > 0) { 917 | FD_ZERO(&wfd); 918 | FD_ZERO(&rfd); 919 | FD_SET(cmdfd, &wfd); 920 | FD_SET(cmdfd, &rfd); 921 | 922 | /* Check if we can write. */ 923 | if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 924 | if (errno == EINTR) 925 | continue; 926 | die("select failed: %s\n", strerror(errno)); 927 | } 928 | if (FD_ISSET(cmdfd, &wfd)) { 929 | /* 930 | * Only write the bytes written by ttywrite() or the 931 | * default of 256. This seems to be a reasonable value 932 | * for a serial line. Bigger values might clog the I/O. 933 | */ 934 | if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 935 | goto write_error; 936 | if (r < n) { 937 | /* 938 | * We weren't able to write out everything. 939 | * This means the buffer is getting full 940 | * again. Empty it. 941 | */ 942 | if (n < lim) 943 | lim = ttyread(); 944 | n -= r; 945 | s += r; 946 | } else { 947 | /* All bytes have been written. */ 948 | break; 949 | } 950 | } 951 | if (FD_ISSET(cmdfd, &rfd)) 952 | lim = ttyread(); 953 | } 954 | return; 955 | 956 | write_error: 957 | die("write error on tty: %s\n", strerror(errno)); 958 | } 959 | 960 | void 961 | ttyresize(int tw, int th) 962 | { 963 | struct winsize w; 964 | 965 | w.ws_row = term.row; 966 | w.ws_col = term.col; 967 | w.ws_xpixel = tw; 968 | w.ws_ypixel = th; 969 | if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 970 | fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 971 | } 972 | 973 | void 974 | ttyhangup(void) 975 | { 976 | /* Send SIGHUP to shell */ 977 | kill(pid, SIGHUP); 978 | } 979 | 980 | int 981 | tattrset(int attr) 982 | { 983 | int i, j; 984 | 985 | for (i = 0; i < term.row-1; i++) { 986 | for (j = 0; j < term.col-1; j++) { 987 | if (term.line[i][j].mode & attr) 988 | return 1; 989 | } 990 | } 991 | 992 | return 0; 993 | } 994 | 995 | void 996 | tsetdirt(int top, int bot) 997 | { 998 | int i; 999 | 1000 | LIMIT(top, 0, term.row-1); 1001 | LIMIT(bot, 0, term.row-1); 1002 | 1003 | for (i = top; i <= bot; i++) 1004 | term.dirty[i] = 1; 1005 | } 1006 | 1007 | void 1008 | tsetdirtattr(int attr) 1009 | { 1010 | int i, j; 1011 | 1012 | for (i = 0; i < term.row-1; i++) { 1013 | for (j = 0; j < term.col-1; j++) { 1014 | if (term.line[i][j].mode & attr) { 1015 | tsetdirt(i, i); 1016 | break; 1017 | } 1018 | } 1019 | } 1020 | } 1021 | 1022 | void 1023 | tfulldirt(void) 1024 | { 1025 | tsetdirt(0, term.row-1); 1026 | } 1027 | 1028 | void 1029 | tcursor(int mode) 1030 | { 1031 | static TCursor c[2]; 1032 | int alt = IS_SET(MODE_ALTSCREEN); 1033 | 1034 | if (mode == CURSOR_SAVE) { 1035 | c[alt] = term.c; 1036 | } else if (mode == CURSOR_LOAD) { 1037 | term.c = c[alt]; 1038 | tmoveto(c[alt].x, c[alt].y); 1039 | } 1040 | } 1041 | 1042 | void 1043 | treset(void) 1044 | { 1045 | uint i; 1046 | 1047 | term.c = (TCursor){{ 1048 | .mode = ATTR_NULL, 1049 | .fg = defaultfg, 1050 | .bg = defaultbg 1051 | }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1052 | 1053 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1054 | for (i = tabspaces; i < term.col; i += tabspaces) 1055 | term.tabs[i] = 1; 1056 | term.top = 0; 1057 | term.bot = term.row - 1; 1058 | term.mode = MODE_WRAP|MODE_UTF8; 1059 | memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1060 | term.charset = 0; 1061 | 1062 | for (i = 0; i < 2; i++) { 1063 | tmoveto(0, 0); 1064 | tcursor(CURSOR_SAVE); 1065 | tclearregion(0, 0, term.col-1, term.row-1); 1066 | tswapscreen(); 1067 | } 1068 | } 1069 | 1070 | void 1071 | tnew(int col, int row) 1072 | { 1073 | term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1074 | tresize(col, row); 1075 | treset(); 1076 | } 1077 | 1078 | void 1079 | tswapscreen(void) 1080 | { 1081 | Line *tmp = term.line; 1082 | 1083 | term.line = term.alt; 1084 | term.alt = tmp; 1085 | term.mode ^= MODE_ALTSCREEN; 1086 | tfulldirt(); 1087 | } 1088 | 1089 | void 1090 | kscrolldown(const Arg* a) 1091 | { 1092 | int n = a->i; 1093 | 1094 | if (n < 0) 1095 | n = term.row + n; 1096 | 1097 | if (n > term.scr) 1098 | n = term.scr; 1099 | 1100 | if (term.scr > 0) { 1101 | term.scr -= n; 1102 | selscroll(0, -n); 1103 | tfulldirt(); 1104 | } 1105 | } 1106 | 1107 | void 1108 | kscrollup(const Arg* a) 1109 | { 1110 | int n = a->i; 1111 | 1112 | if (n < 0) 1113 | n = term.row + n; 1114 | 1115 | if (term.scr <= HISTSIZE-n) { 1116 | term.scr += n; 1117 | selscroll(0, n); 1118 | tfulldirt(); 1119 | } 1120 | } 1121 | 1122 | 1123 | void 1124 | tscrolldown(int orig, int n, int copyhist) 1125 | { 1126 | int i; 1127 | Line temp; 1128 | 1129 | LIMIT(n, 0, term.bot-orig+1); 1130 | 1131 | if (copyhist) { 1132 | term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1133 | temp = term.hist[term.histi]; 1134 | term.hist[term.histi] = term.line[term.bot]; 1135 | term.line[term.bot] = temp; 1136 | } 1137 | 1138 | tsetdirt(orig, term.bot-n); 1139 | tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1140 | 1141 | for (i = term.bot; i >= orig+n; i--) { 1142 | temp = term.line[i]; 1143 | term.line[i] = term.line[i-n]; 1144 | term.line[i-n] = temp; 1145 | } 1146 | 1147 | selscroll(orig, n); 1148 | } 1149 | 1150 | void 1151 | tscrollup(int orig, int n, int copyhist) 1152 | { 1153 | int i; 1154 | Line temp; 1155 | 1156 | LIMIT(n, 0, term.bot-orig+1); 1157 | 1158 | if (copyhist) { 1159 | term.histi = (term.histi + 1) % HISTSIZE; 1160 | temp = term.hist[term.histi]; 1161 | term.hist[term.histi] = term.line[orig]; 1162 | term.line[orig] = temp; 1163 | } 1164 | 1165 | tclearregion(0, orig, term.col-1, orig+n-1); 1166 | tsetdirt(orig+n, term.bot); 1167 | 1168 | for (i = orig; i <= term.bot-n; i++) { 1169 | temp = term.line[i]; 1170 | term.line[i] = term.line[i+n]; 1171 | term.line[i+n] = temp; 1172 | } 1173 | 1174 | selscroll(orig, -n); 1175 | } 1176 | 1177 | void 1178 | selscroll(int orig, int n) 1179 | { 1180 | if (sel.ob.x == -1) 1181 | return; 1182 | 1183 | if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1184 | selclear(); 1185 | } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1186 | sel.ob.y += n; 1187 | sel.oe.y += n; 1188 | if (sel.ob.y < term.top || sel.ob.y > term.bot || 1189 | sel.oe.y < term.top || sel.oe.y > term.bot) { 1190 | selclear(); 1191 | } else { 1192 | selnormalize(); 1193 | } 1194 | } 1195 | } 1196 | 1197 | void 1198 | tnewline(int first_col) 1199 | { 1200 | int y = term.c.y; 1201 | 1202 | if (y == term.bot) { 1203 | tscrollup(term.top, 1, 1); 1204 | } else { 1205 | y++; 1206 | } 1207 | tmoveto(first_col ? 0 : term.c.x, y); 1208 | } 1209 | 1210 | void 1211 | csiparse(void) 1212 | { 1213 | char *p = csiescseq.buf, *np; 1214 | long int v; 1215 | 1216 | csiescseq.narg = 0; 1217 | if (*p == '?') { 1218 | csiescseq.priv = 1; 1219 | p++; 1220 | } 1221 | 1222 | csiescseq.buf[csiescseq.len] = '\0'; 1223 | while (p < csiescseq.buf+csiescseq.len) { 1224 | np = NULL; 1225 | v = strtol(p, &np, 10); 1226 | if (np == p) 1227 | v = 0; 1228 | if (v == LONG_MAX || v == LONG_MIN) 1229 | v = -1; 1230 | csiescseq.arg[csiescseq.narg++] = v; 1231 | p = np; 1232 | if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1233 | break; 1234 | p++; 1235 | } 1236 | csiescseq.mode[0] = *p++; 1237 | csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1238 | } 1239 | 1240 | /* for absolute user moves, when decom is set */ 1241 | void 1242 | tmoveato(int x, int y) 1243 | { 1244 | tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1245 | } 1246 | 1247 | void 1248 | tmoveto(int x, int y) 1249 | { 1250 | int miny, maxy; 1251 | 1252 | if (term.c.state & CURSOR_ORIGIN) { 1253 | miny = term.top; 1254 | maxy = term.bot; 1255 | } else { 1256 | miny = 0; 1257 | maxy = term.row - 1; 1258 | } 1259 | term.c.state &= ~CURSOR_WRAPNEXT; 1260 | term.c.x = LIMIT(x, 0, term.col-1); 1261 | term.c.y = LIMIT(y, miny, maxy); 1262 | } 1263 | 1264 | void 1265 | tsetchar(Rune u, const Glyph *attr, int x, int y) 1266 | { 1267 | static const char *vt100_0[62] = { /* 0x41 - 0x7e */ 1268 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1269 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1270 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1271 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1272 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1273 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1274 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1275 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1276 | }; 1277 | 1278 | /* 1279 | * The table is proudly stolen from rxvt. 1280 | */ 1281 | if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1282 | BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1283 | utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1284 | 1285 | if (term.line[y][x].mode & ATTR_WIDE) { 1286 | if (x+1 < term.col) { 1287 | term.line[y][x+1].u = ' '; 1288 | term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1289 | } 1290 | } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1291 | term.line[y][x-1].u = ' '; 1292 | term.line[y][x-1].mode &= ~ATTR_WIDE; 1293 | } 1294 | 1295 | term.dirty[y] = 1; 1296 | term.line[y][x] = *attr; 1297 | term.line[y][x].u = u; 1298 | } 1299 | 1300 | void 1301 | tclearregion(int x1, int y1, int x2, int y2) 1302 | { 1303 | int x, y, temp; 1304 | Glyph *gp; 1305 | 1306 | if (x1 > x2) 1307 | temp = x1, x1 = x2, x2 = temp; 1308 | if (y1 > y2) 1309 | temp = y1, y1 = y2, y2 = temp; 1310 | 1311 | LIMIT(x1, 0, term.col-1); 1312 | LIMIT(x2, 0, term.col-1); 1313 | LIMIT(y1, 0, term.row-1); 1314 | LIMIT(y2, 0, term.row-1); 1315 | 1316 | for (y = y1; y <= y2; y++) { 1317 | term.dirty[y] = 1; 1318 | for (x = x1; x <= x2; x++) { 1319 | gp = &term.line[y][x]; 1320 | if (selected(x, y)) 1321 | selclear(); 1322 | gp->fg = term.c.attr.fg; 1323 | gp->bg = term.c.attr.bg; 1324 | gp->mode = 0; 1325 | gp->u = ' '; 1326 | } 1327 | } 1328 | } 1329 | 1330 | void 1331 | tdeletechar(int n) 1332 | { 1333 | int dst, src, size; 1334 | Glyph *line; 1335 | 1336 | LIMIT(n, 0, term.col - term.c.x); 1337 | 1338 | dst = term.c.x; 1339 | src = term.c.x + n; 1340 | size = term.col - src; 1341 | line = term.line[term.c.y]; 1342 | 1343 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1344 | tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1345 | } 1346 | 1347 | void 1348 | tinsertblank(int n) 1349 | { 1350 | int dst, src, size; 1351 | Glyph *line; 1352 | 1353 | LIMIT(n, 0, term.col - term.c.x); 1354 | 1355 | dst = term.c.x + n; 1356 | src = term.c.x; 1357 | size = term.col - dst; 1358 | line = term.line[term.c.y]; 1359 | 1360 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1361 | tclearregion(src, term.c.y, dst - 1, term.c.y); 1362 | } 1363 | 1364 | void 1365 | tinsertblankline(int n) 1366 | { 1367 | if (BETWEEN(term.c.y, term.top, term.bot)) 1368 | tscrolldown(term.c.y, n, 0); 1369 | } 1370 | 1371 | void 1372 | tdeleteline(int n) 1373 | { 1374 | if (BETWEEN(term.c.y, term.top, term.bot)) 1375 | tscrollup(term.c.y, n, 0); 1376 | } 1377 | 1378 | int32_t 1379 | tdefcolor(const int *attr, int *npar, int l) 1380 | { 1381 | int32_t idx = -1; 1382 | uint r, g, b; 1383 | 1384 | switch (attr[*npar + 1]) { 1385 | case 2: /* direct color in RGB space */ 1386 | if (*npar + 4 >= l) { 1387 | fprintf(stderr, 1388 | "erresc(38): Incorrect number of parameters (%d)\n", 1389 | *npar); 1390 | break; 1391 | } 1392 | r = attr[*npar + 2]; 1393 | g = attr[*npar + 3]; 1394 | b = attr[*npar + 4]; 1395 | *npar += 4; 1396 | if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1397 | fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1398 | r, g, b); 1399 | else 1400 | idx = TRUECOLOR(r, g, b); 1401 | break; 1402 | case 5: /* indexed color */ 1403 | if (*npar + 2 >= l) { 1404 | fprintf(stderr, 1405 | "erresc(38): Incorrect number of parameters (%d)\n", 1406 | *npar); 1407 | break; 1408 | } 1409 | *npar += 2; 1410 | if (!BETWEEN(attr[*npar], 0, 255)) 1411 | fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1412 | else 1413 | idx = attr[*npar]; 1414 | break; 1415 | case 0: /* implemented defined (only foreground) */ 1416 | case 1: /* transparent */ 1417 | case 3: /* direct color in CMY space */ 1418 | case 4: /* direct color in CMYK space */ 1419 | default: 1420 | fprintf(stderr, 1421 | "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1422 | break; 1423 | } 1424 | 1425 | return idx; 1426 | } 1427 | 1428 | void 1429 | tsetattr(const int *attr, int l) 1430 | { 1431 | int i; 1432 | int32_t idx; 1433 | 1434 | for (i = 0; i < l; i++) { 1435 | switch (attr[i]) { 1436 | case 0: 1437 | term.c.attr.mode &= ~( 1438 | ATTR_BOLD | 1439 | ATTR_FAINT | 1440 | ATTR_ITALIC | 1441 | ATTR_UNDERLINE | 1442 | ATTR_BLINK | 1443 | ATTR_REVERSE | 1444 | ATTR_INVISIBLE | 1445 | ATTR_STRUCK ); 1446 | term.c.attr.fg = defaultfg; 1447 | term.c.attr.bg = defaultbg; 1448 | break; 1449 | case 1: 1450 | term.c.attr.mode |= ATTR_BOLD; 1451 | break; 1452 | case 2: 1453 | term.c.attr.mode |= ATTR_FAINT; 1454 | break; 1455 | case 3: 1456 | term.c.attr.mode |= ATTR_ITALIC; 1457 | break; 1458 | case 4: 1459 | term.c.attr.mode |= ATTR_UNDERLINE; 1460 | break; 1461 | case 5: /* slow blink */ 1462 | /* FALLTHROUGH */ 1463 | case 6: /* rapid blink */ 1464 | term.c.attr.mode |= ATTR_BLINK; 1465 | break; 1466 | case 7: 1467 | term.c.attr.mode |= ATTR_REVERSE; 1468 | break; 1469 | case 8: 1470 | term.c.attr.mode |= ATTR_INVISIBLE; 1471 | break; 1472 | case 9: 1473 | term.c.attr.mode |= ATTR_STRUCK; 1474 | break; 1475 | case 22: 1476 | term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1477 | break; 1478 | case 23: 1479 | term.c.attr.mode &= ~ATTR_ITALIC; 1480 | break; 1481 | case 24: 1482 | term.c.attr.mode &= ~ATTR_UNDERLINE; 1483 | break; 1484 | case 25: 1485 | term.c.attr.mode &= ~ATTR_BLINK; 1486 | break; 1487 | case 27: 1488 | term.c.attr.mode &= ~ATTR_REVERSE; 1489 | break; 1490 | case 28: 1491 | term.c.attr.mode &= ~ATTR_INVISIBLE; 1492 | break; 1493 | case 29: 1494 | term.c.attr.mode &= ~ATTR_STRUCK; 1495 | break; 1496 | case 38: 1497 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1498 | term.c.attr.fg = idx; 1499 | break; 1500 | case 39: 1501 | term.c.attr.fg = defaultfg; 1502 | break; 1503 | case 48: 1504 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1505 | term.c.attr.bg = idx; 1506 | break; 1507 | case 49: 1508 | term.c.attr.bg = defaultbg; 1509 | break; 1510 | default: 1511 | if (BETWEEN(attr[i], 30, 37)) { 1512 | term.c.attr.fg = attr[i] - 30; 1513 | } else if (BETWEEN(attr[i], 40, 47)) { 1514 | term.c.attr.bg = attr[i] - 40; 1515 | } else if (BETWEEN(attr[i], 90, 97)) { 1516 | term.c.attr.fg = attr[i] - 90 + 8; 1517 | } else if (BETWEEN(attr[i], 100, 107)) { 1518 | term.c.attr.bg = attr[i] - 100 + 8; 1519 | } else { 1520 | fprintf(stderr, 1521 | "erresc(default): gfx attr %d unknown\n", 1522 | attr[i]); 1523 | csidump(); 1524 | } 1525 | break; 1526 | } 1527 | } 1528 | } 1529 | 1530 | void 1531 | tsetscroll(int t, int b) 1532 | { 1533 | int temp; 1534 | 1535 | LIMIT(t, 0, term.row-1); 1536 | LIMIT(b, 0, term.row-1); 1537 | if (t > b) { 1538 | temp = t; 1539 | t = b; 1540 | b = temp; 1541 | } 1542 | term.top = t; 1543 | term.bot = b; 1544 | } 1545 | 1546 | void 1547 | tsetmode(int priv, int set, const int *args, int narg) 1548 | { 1549 | int alt; const int *lim; 1550 | 1551 | for (lim = args + narg; args < lim; ++args) { 1552 | if (priv) { 1553 | switch (*args) { 1554 | case 1: /* DECCKM -- Cursor key */ 1555 | xsetmode(set, MODE_APPCURSOR); 1556 | break; 1557 | case 5: /* DECSCNM -- Reverse video */ 1558 | xsetmode(set, MODE_REVERSE); 1559 | break; 1560 | case 6: /* DECOM -- Origin */ 1561 | MODBIT(term.c.state, set, CURSOR_ORIGIN); 1562 | tmoveato(0, 0); 1563 | break; 1564 | case 7: /* DECAWM -- Auto wrap */ 1565 | MODBIT(term.mode, set, MODE_WRAP); 1566 | break; 1567 | case 0: /* Error (IGNORED) */ 1568 | case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1569 | case 3: /* DECCOLM -- Column (IGNORED) */ 1570 | case 4: /* DECSCLM -- Scroll (IGNORED) */ 1571 | case 8: /* DECARM -- Auto repeat (IGNORED) */ 1572 | case 18: /* DECPFF -- Printer feed (IGNORED) */ 1573 | case 19: /* DECPEX -- Printer extent (IGNORED) */ 1574 | case 42: /* DECNRCM -- National characters (IGNORED) */ 1575 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1576 | break; 1577 | case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1578 | xsetmode(!set, MODE_HIDE); 1579 | break; 1580 | case 9: /* X10 mouse compatibility mode */ 1581 | xsetpointermotion(0); 1582 | xsetmode(0, MODE_MOUSE); 1583 | xsetmode(set, MODE_MOUSEX10); 1584 | break; 1585 | case 1000: /* 1000: report button press */ 1586 | xsetpointermotion(0); 1587 | xsetmode(0, MODE_MOUSE); 1588 | xsetmode(set, MODE_MOUSEBTN); 1589 | break; 1590 | case 1002: /* 1002: report motion on button press */ 1591 | xsetpointermotion(0); 1592 | xsetmode(0, MODE_MOUSE); 1593 | xsetmode(set, MODE_MOUSEMOTION); 1594 | break; 1595 | case 1003: /* 1003: enable all mouse motions */ 1596 | xsetpointermotion(set); 1597 | xsetmode(0, MODE_MOUSE); 1598 | xsetmode(set, MODE_MOUSEMANY); 1599 | break; 1600 | case 1004: /* 1004: send focus events to tty */ 1601 | xsetmode(set, MODE_FOCUS); 1602 | break; 1603 | case 1006: /* 1006: extended reporting mode */ 1604 | xsetmode(set, MODE_MOUSESGR); 1605 | break; 1606 | case 1034: 1607 | xsetmode(set, MODE_8BIT); 1608 | break; 1609 | case 1049: /* swap screen & set/restore cursor as xterm */ 1610 | if (!allowaltscreen) 1611 | break; 1612 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1613 | /* FALLTHROUGH */ 1614 | case 47: /* swap screen */ 1615 | case 1047: 1616 | if (!allowaltscreen) 1617 | break; 1618 | alt = IS_SET(MODE_ALTSCREEN); 1619 | if (alt) { 1620 | tclearregion(0, 0, term.col-1, 1621 | term.row-1); 1622 | } 1623 | if (set ^ alt) /* set is always 1 or 0 */ 1624 | tswapscreen(); 1625 | if (*args != 1049) 1626 | break; 1627 | /* FALLTHROUGH */ 1628 | case 1048: 1629 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1630 | break; 1631 | case 2004: /* 2004: bracketed paste mode */ 1632 | xsetmode(set, MODE_BRCKTPASTE); 1633 | break; 1634 | /* Not implemented mouse modes. See comments there. */ 1635 | case 1001: /* mouse highlight mode; can hang the 1636 | terminal by design when implemented. */ 1637 | case 1005: /* UTF-8 mouse mode; will confuse 1638 | applications not supporting UTF-8 1639 | and luit. */ 1640 | case 1015: /* urxvt mangled mouse mode; incompatible 1641 | and can be mistaken for other control 1642 | codes. */ 1643 | break; 1644 | default: 1645 | fprintf(stderr, 1646 | "erresc: unknown private set/reset mode %d\n", 1647 | *args); 1648 | break; 1649 | } 1650 | } else { 1651 | switch (*args) { 1652 | case 0: /* Error (IGNORED) */ 1653 | break; 1654 | case 2: 1655 | xsetmode(set, MODE_KBDLOCK); 1656 | break; 1657 | case 4: /* IRM -- Insertion-replacement */ 1658 | MODBIT(term.mode, set, MODE_INSERT); 1659 | break; 1660 | case 12: /* SRM -- Send/Receive */ 1661 | MODBIT(term.mode, !set, MODE_ECHO); 1662 | break; 1663 | case 20: /* LNM -- Linefeed/new line */ 1664 | MODBIT(term.mode, set, MODE_CRLF); 1665 | break; 1666 | default: 1667 | fprintf(stderr, 1668 | "erresc: unknown set/reset mode %d\n", 1669 | *args); 1670 | break; 1671 | } 1672 | } 1673 | } 1674 | } 1675 | 1676 | void 1677 | csihandle(void) 1678 | { 1679 | char buf[40]; 1680 | int len; 1681 | 1682 | switch (csiescseq.mode[0]) { 1683 | default: 1684 | unknown: 1685 | fprintf(stderr, "erresc: unknown csi "); 1686 | csidump(); 1687 | /* die(""); */ 1688 | break; 1689 | case '@': /* ICH -- Insert blank char */ 1690 | DEFAULT(csiescseq.arg[0], 1); 1691 | tinsertblank(csiescseq.arg[0]); 1692 | break; 1693 | case 'A': /* CUU -- Cursor Up */ 1694 | DEFAULT(csiescseq.arg[0], 1); 1695 | tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1696 | break; 1697 | case 'B': /* CUD -- Cursor Down */ 1698 | case 'e': /* VPR --Cursor Down */ 1699 | DEFAULT(csiescseq.arg[0], 1); 1700 | tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1701 | break; 1702 | case 'i': /* MC -- Media Copy */ 1703 | switch (csiescseq.arg[0]) { 1704 | case 0: 1705 | tdump(); 1706 | break; 1707 | case 1: 1708 | tdumpline(term.c.y); 1709 | break; 1710 | case 2: 1711 | tdumpsel(); 1712 | break; 1713 | case 4: 1714 | term.mode &= ~MODE_PRINT; 1715 | break; 1716 | case 5: 1717 | term.mode |= MODE_PRINT; 1718 | break; 1719 | } 1720 | break; 1721 | case 'c': /* DA -- Device Attributes */ 1722 | if (csiescseq.arg[0] == 0) 1723 | ttywrite(vtiden, strlen(vtiden), 0); 1724 | break; 1725 | case 'b': /* REP -- if last char is printable print it more times */ 1726 | DEFAULT(csiescseq.arg[0], 1); 1727 | if (term.lastc) 1728 | while (csiescseq.arg[0]-- > 0) 1729 | tputc(term.lastc); 1730 | break; 1731 | case 'C': /* CUF -- Cursor Forward */ 1732 | case 'a': /* HPR -- Cursor Forward */ 1733 | DEFAULT(csiescseq.arg[0], 1); 1734 | tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1735 | break; 1736 | case 'D': /* CUB -- Cursor Backward */ 1737 | DEFAULT(csiescseq.arg[0], 1); 1738 | tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1739 | break; 1740 | case 'E': /* CNL -- Cursor Down and first col */ 1741 | DEFAULT(csiescseq.arg[0], 1); 1742 | tmoveto(0, term.c.y+csiescseq.arg[0]); 1743 | break; 1744 | case 'F': /* CPL -- Cursor Up and first col */ 1745 | DEFAULT(csiescseq.arg[0], 1); 1746 | tmoveto(0, term.c.y-csiescseq.arg[0]); 1747 | break; 1748 | case 'g': /* TBC -- Tabulation clear */ 1749 | switch (csiescseq.arg[0]) { 1750 | case 0: /* clear current tab stop */ 1751 | term.tabs[term.c.x] = 0; 1752 | break; 1753 | case 3: /* clear all the tabs */ 1754 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1755 | break; 1756 | default: 1757 | goto unknown; 1758 | } 1759 | break; 1760 | case 'G': /* CHA -- Move to */ 1761 | case '`': /* HPA */ 1762 | DEFAULT(csiescseq.arg[0], 1); 1763 | tmoveto(csiescseq.arg[0]-1, term.c.y); 1764 | break; 1765 | case 'H': /* CUP -- Move to */ 1766 | case 'f': /* HVP */ 1767 | DEFAULT(csiescseq.arg[0], 1); 1768 | DEFAULT(csiescseq.arg[1], 1); 1769 | tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1770 | break; 1771 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 1772 | DEFAULT(csiescseq.arg[0], 1); 1773 | tputtab(csiescseq.arg[0]); 1774 | break; 1775 | case 'J': /* ED -- Clear screen */ 1776 | switch (csiescseq.arg[0]) { 1777 | case 0: /* below */ 1778 | tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1779 | if (term.c.y < term.row-1) { 1780 | tclearregion(0, term.c.y+1, term.col-1, 1781 | term.row-1); 1782 | } 1783 | break; 1784 | case 1: /* above */ 1785 | if (term.c.y > 1) 1786 | tclearregion(0, 0, term.col-1, term.c.y-1); 1787 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1788 | break; 1789 | case 2: /* all */ 1790 | tclearregion(0, 0, term.col-1, term.row-1); 1791 | break; 1792 | default: 1793 | goto unknown; 1794 | } 1795 | break; 1796 | case 'K': /* EL -- Clear line */ 1797 | switch (csiescseq.arg[0]) { 1798 | case 0: /* right */ 1799 | tclearregion(term.c.x, term.c.y, term.col-1, 1800 | term.c.y); 1801 | break; 1802 | case 1: /* left */ 1803 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1804 | break; 1805 | case 2: /* all */ 1806 | tclearregion(0, term.c.y, term.col-1, term.c.y); 1807 | break; 1808 | } 1809 | break; 1810 | case 'S': /* SU -- Scroll line up */ 1811 | DEFAULT(csiescseq.arg[0], 1); 1812 | tscrollup(term.top, csiescseq.arg[0], 0); 1813 | break; 1814 | case 'T': /* SD -- Scroll line down */ 1815 | DEFAULT(csiescseq.arg[0], 1); 1816 | tscrolldown(term.top, csiescseq.arg[0], 0); 1817 | break; 1818 | case 'L': /* IL -- Insert blank lines */ 1819 | DEFAULT(csiescseq.arg[0], 1); 1820 | tinsertblankline(csiescseq.arg[0]); 1821 | break; 1822 | case 'l': /* RM -- Reset Mode */ 1823 | tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1824 | break; 1825 | case 'M': /* DL -- Delete lines */ 1826 | DEFAULT(csiescseq.arg[0], 1); 1827 | tdeleteline(csiescseq.arg[0]); 1828 | break; 1829 | case 'X': /* ECH -- Erase char */ 1830 | DEFAULT(csiescseq.arg[0], 1); 1831 | tclearregion(term.c.x, term.c.y, 1832 | term.c.x + csiescseq.arg[0] - 1, term.c.y); 1833 | break; 1834 | case 'P': /* DCH -- Delete char */ 1835 | DEFAULT(csiescseq.arg[0], 1); 1836 | tdeletechar(csiescseq.arg[0]); 1837 | break; 1838 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 1839 | DEFAULT(csiescseq.arg[0], 1); 1840 | tputtab(-csiescseq.arg[0]); 1841 | break; 1842 | case 'd': /* VPA -- Move to */ 1843 | DEFAULT(csiescseq.arg[0], 1); 1844 | tmoveato(term.c.x, csiescseq.arg[0]-1); 1845 | break; 1846 | case 'h': /* SM -- Set terminal mode */ 1847 | tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1848 | break; 1849 | case 'm': /* SGR -- Terminal attribute (color) */ 1850 | tsetattr(csiescseq.arg, csiescseq.narg); 1851 | break; 1852 | case 'n': /* DSR -- Device Status Report */ 1853 | switch (csiescseq.arg[0]) { 1854 | case 5: /* Status Report "OK" `0n` */ 1855 | ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 1856 | break; 1857 | case 6: /* Report Cursor Position (CPR) ";R" */ 1858 | len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1859 | term.c.y+1, term.c.x+1); 1860 | ttywrite(buf, len, 0); 1861 | break; 1862 | default: 1863 | goto unknown; 1864 | } 1865 | break; 1866 | case 'r': /* DECSTBM -- Set Scrolling Region */ 1867 | if (csiescseq.priv) { 1868 | goto unknown; 1869 | } else { 1870 | DEFAULT(csiescseq.arg[0], 1); 1871 | DEFAULT(csiescseq.arg[1], term.row); 1872 | tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1873 | tmoveato(0, 0); 1874 | } 1875 | break; 1876 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1877 | tcursor(CURSOR_SAVE); 1878 | break; 1879 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1880 | tcursor(CURSOR_LOAD); 1881 | break; 1882 | case ' ': 1883 | switch (csiescseq.mode[1]) { 1884 | case 'q': /* DECSCUSR -- Set Cursor Style */ 1885 | if (xsetcursor(csiescseq.arg[0])) 1886 | goto unknown; 1887 | break; 1888 | default: 1889 | goto unknown; 1890 | } 1891 | break; 1892 | } 1893 | } 1894 | 1895 | void 1896 | csidump(void) 1897 | { 1898 | size_t i; 1899 | uint c; 1900 | 1901 | fprintf(stderr, "ESC["); 1902 | for (i = 0; i < csiescseq.len; i++) { 1903 | c = csiescseq.buf[i] & 0xff; 1904 | if (isprint(c)) { 1905 | putc(c, stderr); 1906 | } else if (c == '\n') { 1907 | fprintf(stderr, "(\\n)"); 1908 | } else if (c == '\r') { 1909 | fprintf(stderr, "(\\r)"); 1910 | } else if (c == 0x1b) { 1911 | fprintf(stderr, "(\\e)"); 1912 | } else { 1913 | fprintf(stderr, "(%02x)", c); 1914 | } 1915 | } 1916 | putc('\n', stderr); 1917 | } 1918 | 1919 | void 1920 | csireset(void) 1921 | { 1922 | memset(&csiescseq, 0, sizeof(csiescseq)); 1923 | } 1924 | 1925 | void 1926 | osc_color_response(int num, int index, int is_osc4) 1927 | { 1928 | int n; 1929 | char buf[32]; 1930 | unsigned char r, g, b; 1931 | 1932 | if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 1933 | fprintf(stderr, "erresc: failed to fetch %s color %d\n", 1934 | is_osc4 ? "osc4" : "osc", 1935 | is_osc4 ? num : index); 1936 | return; 1937 | } 1938 | 1939 | n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1940 | is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 1941 | if (n < 0 || n >= sizeof(buf)) { 1942 | fprintf(stderr, "error: %s while printing %s response\n", 1943 | n < 0 ? "snprintf failed" : "truncation occurred", 1944 | is_osc4 ? "osc4" : "osc"); 1945 | } else { 1946 | ttywrite(buf, n, 1); 1947 | } 1948 | } 1949 | 1950 | void 1951 | strhandle(void) 1952 | { 1953 | char *p = NULL, *dec; 1954 | int j, narg, par; 1955 | const struct { int idx; char *str; } osc_table[] = { 1956 | { defaultfg, "foreground" }, 1957 | { defaultbg, "background" }, 1958 | { defaultcs, "cursor" } 1959 | }; 1960 | 1961 | term.esc &= ~(ESC_STR_END|ESC_STR); 1962 | strparse(); 1963 | par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1964 | 1965 | switch (strescseq.type) { 1966 | case ']': /* OSC -- Operating System Command */ 1967 | switch (par) { 1968 | case 0: 1969 | if (narg > 1) { 1970 | xsettitle(strescseq.args[1]); 1971 | xseticontitle(strescseq.args[1]); 1972 | } 1973 | return; 1974 | case 1: 1975 | if (narg > 1) 1976 | xseticontitle(strescseq.args[1]); 1977 | return; 1978 | case 2: 1979 | if (narg > 1) 1980 | xsettitle(strescseq.args[1]); 1981 | return; 1982 | case 52: 1983 | if (narg > 2 && allowwindowops) { 1984 | dec = base64dec(strescseq.args[2]); 1985 | if (dec) { 1986 | xsetsel(dec); 1987 | xclipcopy(); 1988 | } else { 1989 | fprintf(stderr, "erresc: invalid base64\n"); 1990 | } 1991 | } 1992 | return; 1993 | case 10: 1994 | case 11: 1995 | case 12: 1996 | if (narg < 2) 1997 | break; 1998 | p = strescseq.args[1]; 1999 | if ((j = par - 10) < 0 || j >= LEN(osc_table)) 2000 | break; /* shouldn't be possible */ 2001 | 2002 | if (!strcmp(p, "?")) { 2003 | osc_color_response(par, osc_table[j].idx, 0); 2004 | } else if (xsetcolorname(osc_table[j].idx, p)) { 2005 | fprintf(stderr, "erresc: invalid %s color: %s\n", 2006 | osc_table[j].str, p); 2007 | } else { 2008 | tfulldirt(); 2009 | } 2010 | return; 2011 | case 4: /* color set */ 2012 | if (narg < 3) 2013 | break; 2014 | p = strescseq.args[2]; 2015 | /* FALLTHROUGH */ 2016 | case 104: /* color reset */ 2017 | j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2018 | 2019 | if (p && !strcmp(p, "?")) { 2020 | osc_color_response(j, 0, 1); 2021 | } else if (xsetcolorname(j, p)) { 2022 | if (par == 104 && narg <= 1) { 2023 | xloadcols(); 2024 | return; /* color reset without parameter */ 2025 | } 2026 | fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2027 | j, p ? p : "(null)"); 2028 | } else { 2029 | /* 2030 | * TODO if defaultbg color is changed, borders 2031 | * are dirty 2032 | */ 2033 | tfulldirt(); 2034 | } 2035 | return; 2036 | } 2037 | break; 2038 | case 'k': /* old title set compatibility */ 2039 | xsettitle(strescseq.args[0]); 2040 | return; 2041 | case 'P': /* DCS -- Device Control String */ 2042 | case '_': /* APC -- Application Program Command */ 2043 | case '^': /* PM -- Privacy Message */ 2044 | return; 2045 | } 2046 | 2047 | fprintf(stderr, "erresc: unknown str "); 2048 | strdump(); 2049 | } 2050 | 2051 | void 2052 | strparse(void) 2053 | { 2054 | int c; 2055 | char *p = strescseq.buf; 2056 | 2057 | strescseq.narg = 0; 2058 | strescseq.buf[strescseq.len] = '\0'; 2059 | 2060 | if (*p == '\0') 2061 | return; 2062 | 2063 | while (strescseq.narg < STR_ARG_SIZ) { 2064 | strescseq.args[strescseq.narg++] = p; 2065 | while ((c = *p) != ';' && c != '\0') 2066 | ++p; 2067 | if (c == '\0') 2068 | return; 2069 | *p++ = '\0'; 2070 | } 2071 | } 2072 | 2073 | void 2074 | strdump(void) 2075 | { 2076 | size_t i; 2077 | uint c; 2078 | 2079 | fprintf(stderr, "ESC%c", strescseq.type); 2080 | for (i = 0; i < strescseq.len; i++) { 2081 | c = strescseq.buf[i] & 0xff; 2082 | if (c == '\0') { 2083 | putc('\n', stderr); 2084 | return; 2085 | } else if (isprint(c)) { 2086 | putc(c, stderr); 2087 | } else if (c == '\n') { 2088 | fprintf(stderr, "(\\n)"); 2089 | } else if (c == '\r') { 2090 | fprintf(stderr, "(\\r)"); 2091 | } else if (c == 0x1b) { 2092 | fprintf(stderr, "(\\e)"); 2093 | } else { 2094 | fprintf(stderr, "(%02x)", c); 2095 | } 2096 | } 2097 | fprintf(stderr, "ESC\\\n"); 2098 | } 2099 | 2100 | void 2101 | strreset(void) 2102 | { 2103 | strescseq = (STREscape){ 2104 | .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2105 | .siz = STR_BUF_SIZ, 2106 | }; 2107 | } 2108 | 2109 | void 2110 | sendbreak(const Arg *arg) 2111 | { 2112 | if (tcsendbreak(cmdfd, 0)) 2113 | perror("Error sending break"); 2114 | } 2115 | 2116 | void 2117 | tprinter(char *s, size_t len) 2118 | { 2119 | if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2120 | perror("Error writing to output file"); 2121 | close(iofd); 2122 | iofd = -1; 2123 | } 2124 | } 2125 | 2126 | void 2127 | externalpipe(const Arg *arg) 2128 | { 2129 | int to[2]; 2130 | char buf[UTF_SIZ]; 2131 | void (*oldsigpipe)(int); 2132 | Glyph *bp, *end; 2133 | int lastpos, n, newline; 2134 | 2135 | if (pipe(to) == -1) 2136 | return; 2137 | 2138 | switch (fork()) { 2139 | case -1: 2140 | close(to[0]); 2141 | close(to[1]); 2142 | return; 2143 | case 0: 2144 | dup2(to[0], STDIN_FILENO); 2145 | close(to[0]); 2146 | close(to[1]); 2147 | execvp(((char **)arg->v)[0], (char **)arg->v); 2148 | fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); 2149 | perror("failed"); 2150 | exit(0); 2151 | } 2152 | 2153 | close(to[0]); 2154 | /* ignore sigpipe for now, in case child exists early */ 2155 | oldsigpipe = signal(SIGPIPE, SIG_IGN); 2156 | newline = 0; 2157 | for (n = 0; n < term.row; n++) { 2158 | bp = term.line[n]; 2159 | lastpos = MIN(tlinelen(n) + 1, term.col) - 1; 2160 | if (lastpos < 0) 2161 | break; 2162 | end = &bp[lastpos + 1]; 2163 | for (; bp < end; ++bp) 2164 | if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) 2165 | break; 2166 | if ((newline = term.line[n][lastpos].mode & ATTR_WRAP)) 2167 | continue; 2168 | if (xwrite(to[1], "\n", 1) < 0) 2169 | break; 2170 | newline = 0; 2171 | } 2172 | if (newline) 2173 | (void)xwrite(to[1], "\n", 1); 2174 | close(to[1]); 2175 | /* restore */ 2176 | signal(SIGPIPE, oldsigpipe); 2177 | } 2178 | 2179 | void 2180 | iso14755(const Arg *arg) 2181 | { 2182 | FILE *p; 2183 | char *us, *e, codepoint[9], uc[UTF_SIZ]; 2184 | unsigned long utf32; 2185 | 2186 | if (!(p = popen(ISO14755CMD, "r"))) 2187 | return; 2188 | 2189 | us = fgets(codepoint, sizeof(codepoint), p); 2190 | pclose(p); 2191 | 2192 | if (!us || *us == '\0' || *us == '-' || strlen(us) > 7) 2193 | return; 2194 | if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX || 2195 | (*e != '\n' && *e != '\0')) 2196 | return; 2197 | 2198 | ttywrite(uc, utf8encode(utf32, uc), 1); 2199 | } 2200 | 2201 | void 2202 | toggleprinter(const Arg *arg) 2203 | { 2204 | term.mode ^= MODE_PRINT; 2205 | } 2206 | 2207 | void 2208 | printscreen(const Arg *arg) 2209 | { 2210 | tdump(); 2211 | } 2212 | 2213 | void 2214 | printsel(const Arg *arg) 2215 | { 2216 | tdumpsel(); 2217 | } 2218 | 2219 | void 2220 | tdumpsel(void) 2221 | { 2222 | char *ptr; 2223 | 2224 | if ((ptr = getsel())) { 2225 | tprinter(ptr, strlen(ptr)); 2226 | free(ptr); 2227 | } 2228 | } 2229 | 2230 | void 2231 | tdumpline(int n) 2232 | { 2233 | char buf[UTF_SIZ]; 2234 | const Glyph *bp, *end; 2235 | 2236 | bp = &term.line[n][0]; 2237 | end = &bp[MIN(tlinelen(n), term.col) - 1]; 2238 | if (bp != end || bp->u != ' ') { 2239 | for ( ; bp <= end; ++bp) 2240 | tprinter(buf, utf8encode(bp->u, buf)); 2241 | } 2242 | tprinter("\n", 1); 2243 | } 2244 | 2245 | void 2246 | tdump(void) 2247 | { 2248 | int i; 2249 | 2250 | for (i = 0; i < term.row; ++i) 2251 | tdumpline(i); 2252 | } 2253 | 2254 | void 2255 | tputtab(int n) 2256 | { 2257 | uint x = term.c.x; 2258 | 2259 | if (n > 0) { 2260 | while (x < term.col && n--) 2261 | for (++x; x < term.col && !term.tabs[x]; ++x) 2262 | /* nothing */ ; 2263 | } else if (n < 0) { 2264 | while (x > 0 && n++) 2265 | for (--x; x > 0 && !term.tabs[x]; --x) 2266 | /* nothing */ ; 2267 | } 2268 | term.c.x = LIMIT(x, 0, term.col-1); 2269 | } 2270 | 2271 | void 2272 | tdefutf8(char ascii) 2273 | { 2274 | if (ascii == 'G') 2275 | term.mode |= MODE_UTF8; 2276 | else if (ascii == '@') 2277 | term.mode &= ~MODE_UTF8; 2278 | } 2279 | 2280 | void 2281 | tdeftran(char ascii) 2282 | { 2283 | static char cs[] = "0B"; 2284 | static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2285 | char *p; 2286 | 2287 | if ((p = strchr(cs, ascii)) == NULL) { 2288 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2289 | } else { 2290 | term.trantbl[term.icharset] = vcs[p - cs]; 2291 | } 2292 | } 2293 | 2294 | void 2295 | tdectest(char c) 2296 | { 2297 | int x, y; 2298 | 2299 | if (c == '8') { /* DEC screen alignment test. */ 2300 | for (x = 0; x < term.col; ++x) { 2301 | for (y = 0; y < term.row; ++y) 2302 | tsetchar('E', &term.c.attr, x, y); 2303 | } 2304 | } 2305 | } 2306 | 2307 | void 2308 | tstrsequence(uchar c) 2309 | { 2310 | switch (c) { 2311 | case 0x90: /* DCS -- Device Control String */ 2312 | c = 'P'; 2313 | break; 2314 | case 0x9f: /* APC -- Application Program Command */ 2315 | c = '_'; 2316 | break; 2317 | case 0x9e: /* PM -- Privacy Message */ 2318 | c = '^'; 2319 | break; 2320 | case 0x9d: /* OSC -- Operating System Command */ 2321 | c = ']'; 2322 | break; 2323 | } 2324 | strreset(); 2325 | strescseq.type = c; 2326 | term.esc |= ESC_STR; 2327 | } 2328 | 2329 | void 2330 | tcontrolcode(uchar ascii) 2331 | { 2332 | switch (ascii) { 2333 | case '\t': /* HT */ 2334 | tputtab(1); 2335 | return; 2336 | case '\b': /* BS */ 2337 | tmoveto(term.c.x-1, term.c.y); 2338 | return; 2339 | case '\r': /* CR */ 2340 | tmoveto(0, term.c.y); 2341 | return; 2342 | case '\f': /* LF */ 2343 | case '\v': /* VT */ 2344 | case '\n': /* LF */ 2345 | /* go to first col if the mode is set */ 2346 | tnewline(IS_SET(MODE_CRLF)); 2347 | return; 2348 | case '\a': /* BEL */ 2349 | if (term.esc & ESC_STR_END) { 2350 | /* backwards compatibility to xterm */ 2351 | strhandle(); 2352 | } else { 2353 | xbell(); 2354 | } 2355 | break; 2356 | case '\033': /* ESC */ 2357 | csireset(); 2358 | term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2359 | term.esc |= ESC_START; 2360 | return; 2361 | case '\016': /* SO (LS1 -- Locking shift 1) */ 2362 | case '\017': /* SI (LS0 -- Locking shift 0) */ 2363 | term.charset = 1 - (ascii - '\016'); 2364 | return; 2365 | case '\032': /* SUB */ 2366 | tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2367 | /* FALLTHROUGH */ 2368 | case '\030': /* CAN */ 2369 | csireset(); 2370 | break; 2371 | case '\005': /* ENQ (IGNORED) */ 2372 | case '\000': /* NUL (IGNORED) */ 2373 | case '\021': /* XON (IGNORED) */ 2374 | case '\023': /* XOFF (IGNORED) */ 2375 | case 0177: /* DEL (IGNORED) */ 2376 | return; 2377 | case 0x80: /* TODO: PAD */ 2378 | case 0x81: /* TODO: HOP */ 2379 | case 0x82: /* TODO: BPH */ 2380 | case 0x83: /* TODO: NBH */ 2381 | case 0x84: /* TODO: IND */ 2382 | break; 2383 | case 0x85: /* NEL -- Next line */ 2384 | tnewline(1); /* always go to first col */ 2385 | break; 2386 | case 0x86: /* TODO: SSA */ 2387 | case 0x87: /* TODO: ESA */ 2388 | break; 2389 | case 0x88: /* HTS -- Horizontal tab stop */ 2390 | term.tabs[term.c.x] = 1; 2391 | break; 2392 | case 0x89: /* TODO: HTJ */ 2393 | case 0x8a: /* TODO: VTS */ 2394 | case 0x8b: /* TODO: PLD */ 2395 | case 0x8c: /* TODO: PLU */ 2396 | case 0x8d: /* TODO: RI */ 2397 | case 0x8e: /* TODO: SS2 */ 2398 | case 0x8f: /* TODO: SS3 */ 2399 | case 0x91: /* TODO: PU1 */ 2400 | case 0x92: /* TODO: PU2 */ 2401 | case 0x93: /* TODO: STS */ 2402 | case 0x94: /* TODO: CCH */ 2403 | case 0x95: /* TODO: MW */ 2404 | case 0x96: /* TODO: SPA */ 2405 | case 0x97: /* TODO: EPA */ 2406 | case 0x98: /* TODO: SOS */ 2407 | case 0x99: /* TODO: SGCI */ 2408 | break; 2409 | case 0x9a: /* DECID -- Identify Terminal */ 2410 | ttywrite(vtiden, strlen(vtiden), 0); 2411 | break; 2412 | case 0x9b: /* TODO: CSI */ 2413 | case 0x9c: /* TODO: ST */ 2414 | break; 2415 | case 0x90: /* DCS -- Device Control String */ 2416 | case 0x9d: /* OSC -- Operating System Command */ 2417 | case 0x9e: /* PM -- Privacy Message */ 2418 | case 0x9f: /* APC -- Application Program Command */ 2419 | tstrsequence(ascii); 2420 | return; 2421 | } 2422 | /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2423 | term.esc &= ~(ESC_STR_END|ESC_STR); 2424 | } 2425 | 2426 | /* 2427 | * returns 1 when the sequence is finished and it hasn't to read 2428 | * more characters for this sequence, otherwise 0 2429 | */ 2430 | int 2431 | eschandle(uchar ascii) 2432 | { 2433 | switch (ascii) { 2434 | case '[': 2435 | term.esc |= ESC_CSI; 2436 | return 0; 2437 | case '#': 2438 | term.esc |= ESC_TEST; 2439 | return 0; 2440 | case '%': 2441 | term.esc |= ESC_UTF8; 2442 | return 0; 2443 | case 'P': /* DCS -- Device Control String */ 2444 | case '_': /* APC -- Application Program Command */ 2445 | case '^': /* PM -- Privacy Message */ 2446 | case ']': /* OSC -- Operating System Command */ 2447 | case 'k': /* old title set compatibility */ 2448 | tstrsequence(ascii); 2449 | return 0; 2450 | case 'n': /* LS2 -- Locking shift 2 */ 2451 | case 'o': /* LS3 -- Locking shift 3 */ 2452 | term.charset = 2 + (ascii - 'n'); 2453 | break; 2454 | case '(': /* GZD4 -- set primary charset G0 */ 2455 | case ')': /* G1D4 -- set secondary charset G1 */ 2456 | case '*': /* G2D4 -- set tertiary charset G2 */ 2457 | case '+': /* G3D4 -- set quaternary charset G3 */ 2458 | term.icharset = ascii - '('; 2459 | term.esc |= ESC_ALTCHARSET; 2460 | return 0; 2461 | case 'D': /* IND -- Linefeed */ 2462 | if (term.c.y == term.bot) { 2463 | tscrollup(term.top, 1, 1); 2464 | } else { 2465 | tmoveto(term.c.x, term.c.y+1); 2466 | } 2467 | break; 2468 | case 'E': /* NEL -- Next line */ 2469 | tnewline(1); /* always go to first col */ 2470 | break; 2471 | case 'H': /* HTS -- Horizontal tab stop */ 2472 | term.tabs[term.c.x] = 1; 2473 | break; 2474 | case 'M': /* RI -- Reverse index */ 2475 | if (term.c.y == term.top) { 2476 | tscrolldown(term.top, 1, 1); 2477 | } else { 2478 | tmoveto(term.c.x, term.c.y-1); 2479 | } 2480 | break; 2481 | case 'Z': /* DECID -- Identify Terminal */ 2482 | ttywrite(vtiden, strlen(vtiden), 0); 2483 | break; 2484 | case 'c': /* RIS -- Reset to initial state */ 2485 | treset(); 2486 | resettitle(); 2487 | xloadcols(); 2488 | break; 2489 | case '=': /* DECPAM -- Application keypad */ 2490 | xsetmode(1, MODE_APPKEYPAD); 2491 | break; 2492 | case '>': /* DECPNM -- Normal keypad */ 2493 | xsetmode(0, MODE_APPKEYPAD); 2494 | break; 2495 | case '7': /* DECSC -- Save Cursor */ 2496 | tcursor(CURSOR_SAVE); 2497 | break; 2498 | case '8': /* DECRC -- Restore Cursor */ 2499 | tcursor(CURSOR_LOAD); 2500 | break; 2501 | case '\\': /* ST -- String Terminator */ 2502 | if (term.esc & ESC_STR_END) 2503 | strhandle(); 2504 | break; 2505 | default: 2506 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2507 | (uchar) ascii, isprint(ascii)? ascii:'.'); 2508 | break; 2509 | } 2510 | return 1; 2511 | } 2512 | 2513 | void 2514 | tputc(Rune u) 2515 | { 2516 | char c[UTF_SIZ]; 2517 | int control; 2518 | int width, len; 2519 | Glyph *gp; 2520 | 2521 | control = ISCONTROL(u); 2522 | if (u < 127 || !IS_SET(MODE_UTF8)) { 2523 | c[0] = u; 2524 | width = len = 1; 2525 | } else { 2526 | len = utf8encode(u, c); 2527 | if (!control && (width = wcwidth(u)) == -1) 2528 | width = 1; 2529 | } 2530 | 2531 | if (IS_SET(MODE_PRINT)) 2532 | tprinter(c, len); 2533 | 2534 | /* 2535 | * STR sequence must be checked before anything else 2536 | * because it uses all following characters until it 2537 | * receives a ESC, a SUB, a ST or any other C1 control 2538 | * character. 2539 | */ 2540 | if (term.esc & ESC_STR) { 2541 | if (u == '\a' || u == 030 || u == 032 || u == 033 || 2542 | ISCONTROLC1(u)) { 2543 | term.esc &= ~(ESC_START|ESC_STR); 2544 | term.esc |= ESC_STR_END; 2545 | goto check_control_code; 2546 | } 2547 | 2548 | if (strescseq.len+len >= strescseq.siz) { 2549 | /* 2550 | * Here is a bug in terminals. If the user never sends 2551 | * some code to stop the str or esc command, then st 2552 | * will stop responding. But this is better than 2553 | * silently failing with unknown characters. At least 2554 | * then users will report back. 2555 | * 2556 | * In the case users ever get fixed, here is the code: 2557 | */ 2558 | /* 2559 | * term.esc = 0; 2560 | * strhandle(); 2561 | */ 2562 | if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2563 | return; 2564 | strescseq.siz *= 2; 2565 | strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2566 | } 2567 | 2568 | memmove(&strescseq.buf[strescseq.len], c, len); 2569 | strescseq.len += len; 2570 | return; 2571 | } 2572 | 2573 | check_control_code: 2574 | /* 2575 | * Actions of control codes must be performed as soon they arrive 2576 | * because they can be embedded inside a control sequence, and 2577 | * they must not cause conflicts with sequences. 2578 | */ 2579 | if (control) { 2580 | /* in UTF-8 mode ignore handling C1 control characters */ 2581 | if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2582 | return; 2583 | tcontrolcode(u); 2584 | /* 2585 | * control codes are not shown ever 2586 | */ 2587 | if (!term.esc) 2588 | term.lastc = 0; 2589 | return; 2590 | } else if (term.esc & ESC_START) { 2591 | if (term.esc & ESC_CSI) { 2592 | csiescseq.buf[csiescseq.len++] = u; 2593 | if (BETWEEN(u, 0x40, 0x7E) 2594 | || csiescseq.len >= \ 2595 | sizeof(csiescseq.buf)-1) { 2596 | term.esc = 0; 2597 | csiparse(); 2598 | csihandle(); 2599 | } 2600 | return; 2601 | } else if (term.esc & ESC_UTF8) { 2602 | tdefutf8(u); 2603 | } else if (term.esc & ESC_ALTCHARSET) { 2604 | tdeftran(u); 2605 | } else if (term.esc & ESC_TEST) { 2606 | tdectest(u); 2607 | } else { 2608 | if (!eschandle(u)) 2609 | return; 2610 | /* sequence already finished */ 2611 | } 2612 | term.esc = 0; 2613 | /* 2614 | * All characters which form part of a sequence are not 2615 | * printed 2616 | */ 2617 | return; 2618 | } 2619 | if (selected(term.c.x, term.c.y)) 2620 | selclear(); 2621 | 2622 | gp = &term.line[term.c.y][term.c.x]; 2623 | if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2624 | gp->mode |= ATTR_WRAP; 2625 | tnewline(1); 2626 | gp = &term.line[term.c.y][term.c.x]; 2627 | } 2628 | 2629 | if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2630 | memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2631 | gp->mode &= ~ATTR_WIDE; 2632 | } 2633 | 2634 | if (term.c.x+width > term.col) { 2635 | tnewline(1); 2636 | gp = &term.line[term.c.y][term.c.x]; 2637 | } 2638 | 2639 | tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2640 | term.lastc = u; 2641 | 2642 | if (width == 2) { 2643 | gp->mode |= ATTR_WIDE; 2644 | if (term.c.x+1 < term.col) { 2645 | if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2646 | gp[2].u = ' '; 2647 | gp[2].mode &= ~ATTR_WDUMMY; 2648 | } 2649 | gp[1].u = '\0'; 2650 | gp[1].mode = ATTR_WDUMMY; 2651 | } 2652 | } 2653 | if (term.c.x+width < term.col) { 2654 | tmoveto(term.c.x+width, term.c.y); 2655 | } else { 2656 | term.c.state |= CURSOR_WRAPNEXT; 2657 | } 2658 | } 2659 | 2660 | int 2661 | twrite(const char *buf, int buflen, int show_ctrl) 2662 | { 2663 | int charsize; 2664 | Rune u; 2665 | int n; 2666 | 2667 | for (n = 0; n < buflen; n += charsize) { 2668 | if (IS_SET(MODE_UTF8)) { 2669 | /* process a complete utf8 char */ 2670 | charsize = utf8decode(buf + n, &u, buflen - n); 2671 | if (charsize == 0) 2672 | break; 2673 | } else { 2674 | u = buf[n] & 0xFF; 2675 | charsize = 1; 2676 | } 2677 | if (show_ctrl && ISCONTROL(u)) { 2678 | if (u & 0x80) { 2679 | u &= 0x7f; 2680 | tputc('^'); 2681 | tputc('['); 2682 | } else if (u != '\n' && u != '\r' && u != '\t') { 2683 | u ^= 0x40; 2684 | tputc('^'); 2685 | } 2686 | } 2687 | tputc(u); 2688 | } 2689 | return n; 2690 | } 2691 | 2692 | void 2693 | tresize(int col, int row) 2694 | { 2695 | int i, j; 2696 | int minrow = MIN(row, term.row); 2697 | int mincol = MIN(col, term.col); 2698 | int *bp; 2699 | TCursor c; 2700 | 2701 | if (col < 1 || row < 1) { 2702 | fprintf(stderr, 2703 | "tresize: error resizing to %dx%d\n", col, row); 2704 | return; 2705 | } 2706 | 2707 | /* 2708 | * slide screen to keep cursor where we expect it - 2709 | * tscrollup would work here, but we can optimize to 2710 | * memmove because we're freeing the earlier lines 2711 | */ 2712 | for (i = 0; i <= term.c.y - row; i++) { 2713 | free(term.line[i]); 2714 | free(term.alt[i]); 2715 | } 2716 | /* ensure that both src and dst are not NULL */ 2717 | if (i > 0) { 2718 | memmove(term.line, term.line + i, row * sizeof(Line)); 2719 | memmove(term.alt, term.alt + i, row * sizeof(Line)); 2720 | } 2721 | for (i += row; i < term.row; i++) { 2722 | free(term.line[i]); 2723 | free(term.alt[i]); 2724 | } 2725 | 2726 | /* resize to new height */ 2727 | term.line = xrealloc(term.line, row * sizeof(Line)); 2728 | term.alt = xrealloc(term.alt, row * sizeof(Line)); 2729 | term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2730 | term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2731 | 2732 | for (i = 0; i < HISTSIZE; i++) { 2733 | term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2734 | for (j = mincol; j < col; j++) { 2735 | term.hist[i][j] = term.c.attr; 2736 | term.hist[i][j].u = ' '; 2737 | } 2738 | } 2739 | 2740 | /* resize each r w to new width, zero-pad if needed */ 2741 | for (i = 0; i < minrow; i++) { 2742 | term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2743 | term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2744 | } 2745 | 2746 | /* allocate any new rows */ 2747 | for (/* i = minrow */; i < row; i++) { 2748 | term.line[i] = xmalloc(col * sizeof(Glyph)); 2749 | term.alt[i] = xmalloc(col * sizeof(Glyph)); 2750 | } 2751 | if (col > term.col) { 2752 | bp = term.tabs + term.col; 2753 | 2754 | memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2755 | while (--bp > term.tabs && !*bp) 2756 | /* nothing */ ; 2757 | for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2758 | *bp = 1; 2759 | } 2760 | /* update terminal size */ 2761 | term.col = col; 2762 | term.row = row; 2763 | /* reset scrolling region */ 2764 | tsetscroll(0, row-1); 2765 | /* make use of the LIMIT in tmoveto */ 2766 | tmoveto(term.c.x, term.c.y); 2767 | /* Clearing both screens (it makes dirty all lines) */ 2768 | c = term.c; 2769 | for (i = 0; i < 2; i++) { 2770 | if (mincol < col && 0 < minrow) { 2771 | tclearregion(mincol, 0, col - 1, minrow - 1); 2772 | } 2773 | if (0 < col && minrow < row) { 2774 | tclearregion(0, minrow, col - 1, row - 1); 2775 | } 2776 | tswapscreen(); 2777 | tcursor(CURSOR_LOAD); 2778 | } 2779 | term.c = c; 2780 | } 2781 | 2782 | void 2783 | resettitle(void) 2784 | { 2785 | xsettitle(NULL); 2786 | } 2787 | 2788 | void 2789 | drawregion(int x1, int y1, int x2, int y2) 2790 | { 2791 | int y; 2792 | 2793 | for (y = y1; y < y2; y++) { 2794 | if (!term.dirty[y]) 2795 | continue; 2796 | 2797 | term.dirty[y] = 0; 2798 | xdrawline(TLINE(y), x1, y, x2); 2799 | } 2800 | } 2801 | 2802 | void 2803 | draw(void) 2804 | { 2805 | int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2806 | 2807 | if (!xstartdraw()) 2808 | return; 2809 | 2810 | /* adjust cursor position */ 2811 | LIMIT(term.ocx, 0, term.col-1); 2812 | LIMIT(term.ocy, 0, term.row-1); 2813 | if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2814 | term.ocx--; 2815 | if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2816 | cx--; 2817 | 2818 | drawregion(0, 0, term.col, term.row); 2819 | if (term.scr == 0) 2820 | xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2821 | term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2822 | term.ocx = cx; 2823 | term.ocy = term.c.y; 2824 | xfinishdraw(); 2825 | if (ocx != term.ocx || ocy != term.ocy) 2826 | xximspot(term.ocx, term.ocy); 2827 | } 2828 | 2829 | void 2830 | redraw(void) 2831 | { 2832 | tfulldirt(); 2833 | draw(); 2834 | } 2835 | -------------------------------------------------------------------------------- /st.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | #include 4 | #include 5 | 6 | /* Arbitrary size */ 7 | #define HISTSIZE 2000 8 | 9 | /* macros */ 10 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 11 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 12 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 13 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 14 | #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 15 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 16 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 17 | #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 18 | (a).bg != (b).bg) 19 | #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 20 | (t1.tv_nsec-t2.tv_nsec)/1E6) 21 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 22 | 23 | #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 24 | #define IS_TRUECOL(x) (1 << 24 & (x)) 25 | #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - term.scr \ 26 | + HISTSIZE + 1) % HISTSIZE] : term.line[(y) - term.scr]) 27 | 28 | enum glyph_attribute { 29 | ATTR_NULL = 0, 30 | ATTR_BOLD = 1 << 0, 31 | ATTR_FAINT = 1 << 1, 32 | ATTR_ITALIC = 1 << 2, 33 | ATTR_UNDERLINE = 1 << 3, 34 | ATTR_BLINK = 1 << 4, 35 | ATTR_REVERSE = 1 << 5, 36 | ATTR_INVISIBLE = 1 << 6, 37 | ATTR_STRUCK = 1 << 7, 38 | ATTR_WRAP = 1 << 8, 39 | ATTR_WIDE = 1 << 9, 40 | ATTR_WDUMMY = 1 << 10, 41 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 42 | }; 43 | 44 | enum selection_mode { 45 | SEL_IDLE = 0, 46 | SEL_EMPTY = 1, 47 | SEL_READY = 2 48 | }; 49 | 50 | enum selection_type { 51 | SEL_REGULAR = 1, 52 | SEL_RECTANGULAR = 2 53 | }; 54 | 55 | enum selection_snap { 56 | SNAP_WORD = 1, 57 | SNAP_LINE = 2 58 | }; 59 | 60 | typedef void (*CleanFunc)(); 61 | 62 | typedef unsigned char uchar; 63 | typedef unsigned int uint; 64 | typedef unsigned long ulong; 65 | typedef unsigned short ushort; 66 | 67 | typedef uint_least32_t Rune; 68 | 69 | #define Glyph Glyph_ 70 | typedef struct { 71 | Rune u; /* character code */ 72 | ushort mode; /* attribute flags */ 73 | uint32_t fg; /* foreground */ 74 | uint32_t bg; /* background */ 75 | } Glyph; 76 | 77 | typedef Glyph *Line; 78 | 79 | typedef union { 80 | int i; 81 | uint ui; 82 | float f; 83 | const void *v; 84 | const char *s; 85 | } Arg; 86 | 87 | void die(const char *, ...); 88 | void redraw(void); 89 | void draw(void); 90 | 91 | void externalpipe(const Arg *); 92 | void iso14755(const Arg *); 93 | void printscreen(const Arg *); 94 | void printsel(const Arg *); 95 | void sendbreak(const Arg *); 96 | void toggleprinter(const Arg *); 97 | 98 | int tattrset(int); 99 | void tnew(int, int); 100 | void tresize(int, int); 101 | void tsetdirtattr(int); 102 | void ttyhangup(void); 103 | int ttynew(const char *, char *, const char *, char **); 104 | size_t ttyread(void); 105 | void ttyresize(int, int); 106 | void ttywrite(const char *, size_t, int); 107 | 108 | void resettitle(void); 109 | 110 | void selclear(void); 111 | void selinit(void); 112 | void selstart(int, int, int); 113 | void selextend(int, int, int, int); 114 | int selected(int, int); 115 | char *getsel(void); 116 | 117 | size_t utf8encode(Rune, char *); 118 | 119 | void *xmalloc(size_t); 120 | void *xrealloc(void *, size_t); 121 | char *xstrdup(const char *); 122 | 123 | void kscrolldown(const Arg *); 124 | void kscrollup(const Arg *); 125 | 126 | void addcleanfunc(CleanFunc); 127 | 128 | /* config.h globals */ 129 | extern char *utmp; 130 | extern char *scroll; 131 | extern char *stty_args; 132 | extern char *vtiden; 133 | extern wchar_t *worddelimiters; 134 | extern int allowaltscreen; 135 | extern int allowwindowops; 136 | extern char *termname; 137 | extern unsigned int tabspaces; 138 | extern unsigned int defaultfg; 139 | extern unsigned int defaultbg; 140 | extern unsigned int defaultcs; 141 | -------------------------------------------------------------------------------- /st.info: -------------------------------------------------------------------------------- 1 | st-mono| simpleterm monocolor, 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#2, 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 | rin=\E[%p1%dT, 162 | ritm=\E[23m, 163 | rmacs=\E(B, 164 | rmcup=\E[?1049l, 165 | rmir=\E[4l, 166 | rmkx=\E[?1l\E>, 167 | rmso=\E[27m, 168 | rmul=\E[24m, 169 | rs1=\Ec, 170 | rs2=\E[4l\E>\E[?1034l, 171 | sc=\E7, 172 | sitm=\E[3m, 173 | sgr0=\E[0m, 174 | smacs=\E(0, 175 | smcup=\E[?1049h, 176 | smir=\E[4h, 177 | smkx=\E[?1h\E=, 178 | smso=\E[7m, 179 | smul=\E[4m, 180 | tbc=\E[3g, 181 | tsl=\E]0;, 182 | xenl, 183 | vpa=\E[%i%p1%dd, 184 | # XTerm extensions 185 | rmxx=\E[29m, 186 | smxx=\E[9m, 187 | # disabled rep for now: causes some issues with older ncurses versions. 188 | # rep=%p1%c\E[%p2%{1}%-%db, 189 | # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) 190 | Tc, 191 | Ms=\E]52;%p1%s;%p2%s\007, 192 | Se=\E[2 q, 193 | Ss=\E[%p1%d q, 194 | 195 | st| simpleterm, 196 | use=st-mono, 197 | colors#8, 198 | setab=\E[4%p1%dm, 199 | setaf=\E[3%p1%dm, 200 | setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 201 | setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 202 | 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, 203 | 204 | st-256color| simpleterm with 256 colors, 205 | use=st, 206 | ccc, 207 | colors#256, 208 | oc=\E]104\007, 209 | pairs#32767, 210 | # Nicked from xterm-256color 211 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 212 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 213 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 214 | 215 | st-meta| simpleterm with meta key, 216 | use=st, 217 | km, 218 | rmm=\E[?1034l, 219 | smm=\E[?1034h, 220 | rs2=\E[4l\E>\E[?1034h, 221 | is2=\E[4l\E>\E[?1034h, 222 | 223 | st-meta-256color| simpleterm with meta key and 256 colors, 224 | use=st-256color, 225 | km, 226 | rmm=\E[?1034l, 227 | smm=\E[?1034h, 228 | rs2=\E[4l\E>\E[?1034h, 229 | is2=\E[4l\E>\E[?1034h, 230 | 231 | st-bs| simpleterm with backspace as backspace, 232 | use=st, 233 | kbs=\010, 234 | kdch1=\177, 235 | 236 | st-bs-256color| simpleterm with backspace as backspace and 256colors, 237 | use=st-256color, 238 | kbs=\010, 239 | kdch1=\177, 240 | -------------------------------------------------------------------------------- /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 | int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); 34 | void xseticontitle(char *); 35 | void xsettitle(char *); 36 | int xsetcursor(int); 37 | void xsetmode(int, unsigned int); 38 | void xsetpointermotion(int); 39 | void xsetsel(char *); 40 | int xstartdraw(void); 41 | void xximspot(int, int); 42 | -------------------------------------------------------------------------------- /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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | char *argv0; 27 | #include "arg.h" 28 | #include "st.h" 29 | #include "win.h" 30 | 31 | /* types used in config.h */ 32 | typedef struct { 33 | uint mod; 34 | KeySym keysym; 35 | void (*func)(const Arg *); 36 | const Arg arg; 37 | } Shortcut; 38 | 39 | typedef struct { 40 | uint mod; 41 | uint button; 42 | void (*func)(const Arg *); 43 | const Arg arg; 44 | uint release; 45 | } MouseShortcut; 46 | 47 | typedef struct { 48 | KeySym k; 49 | uint mask; 50 | char *s; 51 | /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ 52 | signed char appkey; /* application keypad */ 53 | signed char appcursor; /* application cursor */ 54 | } Key; 55 | 56 | /* Xresources preferences */ 57 | enum resource_type { 58 | STRING = 0, 59 | INTEGER = 1, 60 | FLOAT = 2 61 | }; 62 | 63 | typedef struct { 64 | char *name; 65 | enum resource_type type; 66 | void *dst; 67 | } ResourcePref; 68 | 69 | /* X modifiers */ 70 | #define XK_ANY_MOD UINT_MAX 71 | #define XK_NO_MOD 0 72 | #define XK_SWITCH_MOD (1<<13|1<<14) 73 | 74 | /* function definitions used in config.h */ 75 | static void clipcopy(const Arg *); 76 | static void clippaste(const Arg *); 77 | static void numlock(const Arg *); 78 | static void selpaste(const Arg *); 79 | static void zoom(const Arg *); 80 | static void zoomabs(const Arg *); 81 | static void zoomreset(const Arg *); 82 | static void ttysend(const Arg *); 83 | 84 | /* config.h for applying patches and the configuration. */ 85 | #include "config.h" 86 | 87 | /* XEMBED messages */ 88 | #define XEMBED_FOCUS_IN 4 89 | #define XEMBED_FOCUS_OUT 5 90 | 91 | /* macros */ 92 | #define IS_SET(flag) ((win.mode & (flag)) != 0) 93 | #define TRUERED(x) (((x) & 0xff0000) >> 8) 94 | #define TRUEGREEN(x) (((x) & 0xff00)) 95 | #define TRUEBLUE(x) (((x) & 0xff) << 8) 96 | #define CONVCOL(x) (double)((x) / 65535.0) 97 | 98 | typedef cairo_t *Draw; 99 | typedef XColor Color; 100 | 101 | typedef struct { 102 | FT_Face face; 103 | cairo_font_face_t *cairo; 104 | FcPattern *pattern; 105 | } MatchFont; 106 | 107 | typedef struct { 108 | MatchFont *font; 109 | cairo_glyph_t glyph; 110 | } GlyphFontSpec; 111 | 112 | typedef struct { 113 | XVaNestedList *list; 114 | XFontSet fontset; 115 | } XIMPreeditAttrs; 116 | 117 | /* Purely graphic info */ 118 | typedef struct { 119 | int tw, th; /* tty width and height */ 120 | int w, h; /* window width and height */ 121 | int ch; /* char height */ 122 | int cw; /* char width */ 123 | int cyo; /* char y offset */ 124 | int mode; /* window state/mode flags */ 125 | int cursor; /* cursor style */ 126 | } TermWindow; 127 | 128 | typedef struct { 129 | Display *dpy; 130 | Colormap cmap; 131 | Window win; 132 | Drawable buf; 133 | GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 134 | Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; 135 | struct { 136 | XIM xim; 137 | XIC xic; 138 | XPoint spot; 139 | XVaNestedList spotlist; 140 | } ime; 141 | Draw draw; 142 | Visual *vis; 143 | XSetWindowAttributes attrs; 144 | int scr; 145 | int isfixed; /* is fixed geometry? */ 146 | int l, t; /* left and top offset */ 147 | int gm; /* geometry mask */ 148 | } XWindow; 149 | 150 | typedef struct { 151 | Atom xtarget; 152 | char *primary, *clipboard; 153 | struct timespec tclick1; 154 | struct timespec tclick2; 155 | } XSelection; 156 | 157 | /* Font structure */ 158 | #define Font Font_ 159 | typedef struct { 160 | int height; 161 | int width; 162 | int ascent; 163 | int descent; 164 | int badslant; 165 | int badweight; 166 | short lbearing; 167 | short rbearing; 168 | MatchFont match; 169 | FcFontSet *set; 170 | FcPattern *pattern; 171 | } Font; 172 | 173 | /* Drawing Context */ 174 | typedef struct { 175 | Color *col; 176 | size_t collen; 177 | Font font, bfont, ifont, ibfont; 178 | GC gc; 179 | } DC; 180 | 181 | static inline ushort sixd_to_16bit(int); 182 | static int xmakeglyphfontspecs(GlyphFontSpec *, const Glyph *, int, int, int); 183 | static void xdrawglyphfontspecs(const GlyphFontSpec *, Glyph, int, int, int); 184 | static void xdrawglyph(Glyph, int, int); 185 | static void xdrawrect(XColor *, int, int, int, int); 186 | static void xdrawglyphfontspec(XColor *, const GlyphFontSpec *, int); 187 | static void xclear(int, int, int, int); 188 | static int xgeommasktogravity(int); 189 | static int ximopen(Display *); 190 | static XIMStyle ximpickupstyle(XIM); 191 | static XIMPreeditAttrs *ximcreatepreeditattrs(Display *, XIMStyle); 192 | static void ximdestroypreeditattrs(Display *, XIMPreeditAttrs *); 193 | static void ximinstantiate(Display *, XPointer, XPointer); 194 | static void ximdestroy(XIM, XPointer, XPointer); 195 | static int xicdestroy(XIC, XPointer, XPointer); 196 | static void xinit(int, int); 197 | static void cresize(int, int); 198 | static void xresize(int, int); 199 | static void xhints(void); 200 | static int xloadcolor(int, const char *, Color *); 201 | static int xloadfont(Font *, FcPattern *); 202 | static void xloadfonts(const char *, double); 203 | static void xunloadfont(Font *); 204 | static void xunloadfonts(void); 205 | static void xsetenv(void); 206 | static void xseturgency(int); 207 | static int evcol(XEvent *); 208 | static int evrow(XEvent *); 209 | 210 | static void expose(XEvent *); 211 | static void visibility(XEvent *); 212 | static void unmap(XEvent *); 213 | static void kpress(XEvent *); 214 | static void cmessage(XEvent *); 215 | static void resize(XEvent *); 216 | static void focus(XEvent *); 217 | static uint buttonmask(uint); 218 | static int mouseaction(XEvent *, uint); 219 | static void brelease(XEvent *); 220 | static void bpress(XEvent *); 221 | static void bmotion(XEvent *); 222 | static void propnotify(XEvent *); 223 | static void selnotify(XEvent *); 224 | static void selclear_(XEvent *); 225 | static void selrequest(XEvent *); 226 | static void setsel(char *, Time); 227 | static void mousesel(XEvent *, int); 228 | static void mousereport(XEvent *); 229 | static char *kmap(KeySym, uint); 230 | static int match(uint, uint); 231 | 232 | static void run(void); 233 | static void usage(void); 234 | static void stoploop(); 235 | static void cleanup(); 236 | static void configinit(Display *); 237 | 238 | static void (*handler[LASTEvent])(XEvent *) = { 239 | [KeyPress] = kpress, 240 | [ClientMessage] = cmessage, 241 | [ConfigureNotify] = resize, 242 | [VisibilityNotify] = visibility, 243 | [UnmapNotify] = unmap, 244 | [Expose] = expose, 245 | [FocusIn] = focus, 246 | [FocusOut] = focus, 247 | [MotionNotify] = bmotion, 248 | [ButtonPress] = bpress, 249 | [ButtonRelease] = brelease, 250 | /* 251 | * Uncomment if you want the selection to disappear when you select something 252 | * different in another window. 253 | */ 254 | /* [SelectionClear] = selclear_, */ 255 | [SelectionNotify] = selnotify, 256 | /* 257 | * PropertyNotify is only turned on when there is some INCR transfer happening 258 | * for the selection retrieval. 259 | */ 260 | [PropertyNotify] = propnotify, 261 | [SelectionRequest] = selrequest, 262 | }; 263 | 264 | /* Globals */ 265 | static DC dc; 266 | static XWindow xw; 267 | static XSelection xsel; 268 | static TermWindow win; 269 | static XrmDatabase db; 270 | static int canloop = 1; 271 | 272 | /* Supported XIM Styles */ 273 | static XIMStyle ximstyles[] = { 274 | (XIMPreeditPosition | XIMStatusNothing), // Over-the-spot 275 | (XIMPreeditNothing | XIMStatusNothing) // Root 276 | }; 277 | 278 | /* Font Ring Cache */ 279 | enum { 280 | FRC_NORMAL, 281 | FRC_ITALIC, 282 | FRC_BOLD, 283 | FRC_ITALICBOLD 284 | }; 285 | 286 | typedef struct { 287 | MatchFont font; 288 | int flags; 289 | Rune unicodep; 290 | } Fontcache; 291 | 292 | /* Fontcache is an array now. A new font will be appended to the array. */ 293 | static Fontcache *frc = NULL; 294 | static int frclen = 0; 295 | static int frccap = 0; 296 | static char *usedfont = NULL; 297 | static double usedfontsize = 0; 298 | static double defaultfontsize = 0; 299 | static FT_Library ftlib; 300 | 301 | static char *opt_class = NULL; 302 | static char **opt_cmd = NULL; 303 | static char *opt_embed = NULL; 304 | static char *opt_font = NULL; 305 | static char *opt_io = NULL; 306 | static char *opt_line = NULL; 307 | static char *opt_name = NULL; 308 | static char *opt_title = NULL; 309 | 310 | static uint buttons; /* bit field of pressed buttons */ 311 | 312 | void 313 | clipcopy(const Arg *dummy) 314 | { 315 | Atom clipboard; 316 | 317 | free(xsel.clipboard); 318 | xsel.clipboard = NULL; 319 | 320 | if (xsel.primary != NULL) { 321 | xsel.clipboard = xstrdup(xsel.primary); 322 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 323 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 324 | } 325 | } 326 | 327 | void 328 | clippaste(const Arg *dummy) 329 | { 330 | Atom clipboard; 331 | 332 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 333 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, 334 | xw.win, CurrentTime); 335 | } 336 | 337 | void 338 | selpaste(const Arg *dummy) 339 | { 340 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, 341 | xw.win, CurrentTime); 342 | } 343 | 344 | void 345 | numlock(const Arg *dummy) 346 | { 347 | win.mode ^= MODE_NUMLOCK; 348 | } 349 | 350 | void 351 | zoom(const Arg *arg) 352 | { 353 | Arg larg; 354 | 355 | larg.f = usedfontsize + arg->f; 356 | zoomabs(&larg); 357 | } 358 | 359 | void 360 | zoomabs(const Arg *arg) 361 | { 362 | xunloadfonts(); 363 | xloadfonts(usedfont, arg->f); 364 | cresize(0, 0); 365 | redraw(); 366 | xhints(); 367 | } 368 | 369 | void 370 | zoomreset(const Arg *arg) 371 | { 372 | Arg larg; 373 | 374 | if (defaultfontsize > 0) { 375 | larg.f = defaultfontsize; 376 | zoomabs(&larg); 377 | } 378 | } 379 | 380 | void 381 | ttysend(const Arg *arg) 382 | { 383 | ttywrite(arg->s, strlen(arg->s), 1); 384 | } 385 | 386 | int 387 | evcol(XEvent *e) 388 | { 389 | int x = e->xbutton.x - borderpx; 390 | LIMIT(x, 0, win.tw - 1); 391 | return x / win.cw; 392 | } 393 | 394 | int 395 | evrow(XEvent *e) 396 | { 397 | int y = e->xbutton.y - borderpx; 398 | LIMIT(y, 0, win.th - 1); 399 | return y / win.ch; 400 | } 401 | 402 | void 403 | mousesel(XEvent *e, int done) 404 | { 405 | int type, seltype = SEL_REGULAR; 406 | uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); 407 | 408 | for (type = 1; type < LEN(selmasks); ++type) { 409 | if (match(selmasks[type], state)) { 410 | seltype = type; 411 | break; 412 | } 413 | } 414 | selextend(evcol(e), evrow(e), seltype, done); 415 | if (done) 416 | setsel(getsel(), e->xbutton.time); 417 | } 418 | 419 | void 420 | mousereport(XEvent *e) 421 | { 422 | int len, btn, code; 423 | int x = evcol(e), y = evrow(e); 424 | int state = e->xbutton.state; 425 | char buf[40]; 426 | static int ox, oy; 427 | 428 | if (e->type == MotionNotify) { 429 | if (x == ox && y == oy) 430 | return; 431 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 432 | return; 433 | /* MODE_MOUSEMOTION: no reporting if no button is pressed */ 434 | if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) 435 | return; 436 | /* Set btn to lowest-numbered pressed button, or 12 if no 437 | * buttons are pressed. */ 438 | for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) 439 | ; 440 | code = 32; 441 | } else { 442 | btn = e->xbutton.button; 443 | /* Only buttons 1 through 11 can be encoded */ 444 | if (btn < 1 || btn > 11) 445 | return; 446 | if (e->type == ButtonRelease) { 447 | /* MODE_MOUSEX10: no button release reporting */ 448 | if (IS_SET(MODE_MOUSEX10)) 449 | return; 450 | /* Don't send release events for the scroll wheel */ 451 | if (btn == 4 || btn == 5) 452 | return; 453 | } 454 | code = 0; 455 | } 456 | 457 | ox = x; 458 | oy = y; 459 | 460 | /* Encode btn into code. If no button is pressed for a motion event in 461 | * MODE_MOUSEMANY, then encode it as a release. */ 462 | if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) 463 | code += 3; 464 | else if (btn >= 8) 465 | code += 128 + btn - 8; 466 | else if (btn >= 4) 467 | code += 64 + btn - 4; 468 | else 469 | code += btn - 1; 470 | 471 | if (!IS_SET(MODE_MOUSEX10)) { 472 | code += ((state & ShiftMask ) ? 4 : 0) 473 | + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ 474 | + ((state & ControlMask) ? 16 : 0); 475 | } 476 | 477 | if (IS_SET(MODE_MOUSESGR)) { 478 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 479 | code, x+1, y+1, 480 | e->type == ButtonRelease ? 'm' : 'M'); 481 | } else if (x < 223 && y < 223) { 482 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 483 | 32+code, 32+x+1, 32+y+1); 484 | } else { 485 | return; 486 | } 487 | 488 | ttywrite(buf, len, 0); 489 | } 490 | 491 | uint 492 | buttonmask(uint button) 493 | { 494 | return button == Button1 ? Button1Mask 495 | : button == Button2 ? Button2Mask 496 | : button == Button3 ? Button3Mask 497 | : button == Button4 ? Button4Mask 498 | : button == Button5 ? Button5Mask 499 | : 0; 500 | } 501 | 502 | int 503 | mouseaction(XEvent *e, uint release) 504 | { 505 | MouseShortcut *ms; 506 | 507 | /* ignore Buttonmask for Button - it's set on release */ 508 | uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 509 | 510 | for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 511 | if (ms->release == release && 512 | ms->button == e->xbutton.button && 513 | (match(ms->mod, state) || /* exact or forced */ 514 | match(ms->mod, state & ~forcemousemod))) { 515 | ms->func(&(ms->arg)); 516 | return 1; 517 | } 518 | } 519 | 520 | return 0; 521 | } 522 | 523 | void 524 | bpress(XEvent *e) 525 | { 526 | int btn = e->xbutton.button; 527 | struct timespec now; 528 | int snap; 529 | 530 | if (1 <= btn && btn <= 11) 531 | buttons |= 1 << (btn-1); 532 | 533 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 534 | mousereport(e); 535 | return; 536 | } 537 | 538 | if (mouseaction(e, 0)) 539 | return; 540 | 541 | if (btn == Button1) { 542 | /* 543 | * If the user clicks below predefined timeouts specific 544 | * snapping behaviour is exposed. 545 | */ 546 | clock_gettime(CLOCK_MONOTONIC, &now); 547 | if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { 548 | snap = SNAP_LINE; 549 | } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { 550 | snap = SNAP_WORD; 551 | } else { 552 | snap = 0; 553 | } 554 | xsel.tclick2 = xsel.tclick1; 555 | xsel.tclick1 = now; 556 | 557 | selstart(evcol(e), evrow(e), snap); 558 | } 559 | } 560 | 561 | void 562 | propnotify(XEvent *e) 563 | { 564 | XPropertyEvent *xpev; 565 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 566 | 567 | xpev = &e->xproperty; 568 | if (xpev->state == PropertyNewValue && 569 | (xpev->atom == XA_PRIMARY || 570 | xpev->atom == clipboard)) { 571 | selnotify(e); 572 | } 573 | } 574 | 575 | void 576 | selnotify(XEvent *e) 577 | { 578 | ulong nitems, ofs, rem; 579 | int format; 580 | uchar *data, *last, *repl; 581 | Atom type, incratom, property = None; 582 | 583 | incratom = XInternAtom(xw.dpy, "INCR", 0); 584 | 585 | ofs = 0; 586 | if (e->type == SelectionNotify) 587 | property = e->xselection.property; 588 | else if (e->type == PropertyNotify) 589 | property = e->xproperty.atom; 590 | 591 | if (property == None) 592 | return; 593 | 594 | do { 595 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 596 | BUFSIZ/4, False, AnyPropertyType, 597 | &type, &format, &nitems, &rem, 598 | &data)) { 599 | fprintf(stderr, "Clipboard allocation failed\n"); 600 | return; 601 | } 602 | 603 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 604 | /* 605 | * If there is some PropertyNotify with no data, then 606 | * this is the signal of the selection owner that all 607 | * data has been transferred. We won't need to receive 608 | * PropertyNotify events anymore. 609 | */ 610 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 611 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 612 | &xw.attrs); 613 | } 614 | 615 | if (type == incratom) { 616 | /* 617 | * Activate the PropertyNotify events so we receive 618 | * when the selection owner does send us the next 619 | * chunk of data. 620 | */ 621 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 622 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 623 | &xw.attrs); 624 | 625 | /* 626 | * Deleting the property is the transfer start signal. 627 | */ 628 | XDeleteProperty(xw.dpy, xw.win, (int)property); 629 | continue; 630 | } 631 | 632 | /* 633 | * As seen in getsel: 634 | * Line endings are inconsistent in the terminal and GUI world 635 | * copy and pasting. When receiving some selection data, 636 | * replace all '\n' with '\r'. 637 | * FIXME: Fix the computer world. 638 | */ 639 | repl = data; 640 | last = data + nitems * format / 8; 641 | while ((repl = memchr(repl, '\n', last - repl))) { 642 | *repl++ = '\r'; 643 | } 644 | 645 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 646 | ttywrite("\033[200~", 6, 0); 647 | ttywrite((char *)data, nitems * format / 8, 1); 648 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 649 | ttywrite("\033[201~", 6, 0); 650 | XFree(data); 651 | /* number of 32-bit chunks returned */ 652 | ofs += nitems * format / 32; 653 | } while (rem > 0); 654 | 655 | /* 656 | * Deleting the property again tells the selection owner to send the 657 | * next data chunk in the property. 658 | */ 659 | XDeleteProperty(xw.dpy, xw.win, (int)property); 660 | } 661 | 662 | void 663 | xclipcopy(void) 664 | { 665 | clipcopy(NULL); 666 | } 667 | 668 | void 669 | selclear_(XEvent *e) 670 | { 671 | selclear(); 672 | } 673 | 674 | void 675 | selrequest(XEvent *e) 676 | { 677 | XSelectionRequestEvent *xsre; 678 | XSelectionEvent xev; 679 | Atom xa_targets, string, clipboard; 680 | char *seltext; 681 | 682 | xsre = (XSelectionRequestEvent *) e; 683 | xev.type = SelectionNotify; 684 | xev.requestor = xsre->requestor; 685 | xev.selection = xsre->selection; 686 | xev.target = xsre->target; 687 | xev.time = xsre->time; 688 | if (xsre->property == None) 689 | xsre->property = xsre->target; 690 | 691 | /* reject */ 692 | xev.property = None; 693 | 694 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 695 | if (xsre->target == xa_targets) { 696 | /* respond with the supported type */ 697 | string = xsel.xtarget; 698 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 699 | XA_ATOM, 32, PropModeReplace, 700 | (uchar *) &string, 1); 701 | xev.property = xsre->property; 702 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 703 | /* 704 | * xith XA_STRING non ascii characters may be incorrect in the 705 | * requestor. It is not our problem, use utf8. 706 | */ 707 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 708 | if (xsre->selection == XA_PRIMARY) { 709 | seltext = xsel.primary; 710 | } else if (xsre->selection == clipboard) { 711 | seltext = xsel.clipboard; 712 | } else { 713 | fprintf(stderr, 714 | "Unhandled clipboard selection 0x%lx\n", 715 | xsre->selection); 716 | return; 717 | } 718 | if (seltext != NULL) { 719 | XChangeProperty(xsre->display, xsre->requestor, 720 | xsre->property, xsre->target, 721 | 8, PropModeReplace, 722 | (uchar *)seltext, strlen(seltext)); 723 | xev.property = xsre->property; 724 | } 725 | } 726 | 727 | /* all done, send a notification to the listener */ 728 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 729 | fprintf(stderr, "Error sending SelectionNotify event\n"); 730 | } 731 | 732 | void 733 | setsel(char *str, Time t) 734 | { 735 | if (!str) 736 | return; 737 | 738 | free(xsel.primary); 739 | xsel.primary = str; 740 | 741 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 742 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 743 | selclear(); 744 | } 745 | 746 | void 747 | xsetsel(char *str) 748 | { 749 | setsel(str, CurrentTime); 750 | } 751 | 752 | void 753 | brelease(XEvent *e) 754 | { 755 | int btn = e->xbutton.button; 756 | 757 | if (1 <= btn && btn <= 11) 758 | buttons &= ~(1 << (btn-1)); 759 | 760 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 761 | mousereport(e); 762 | return; 763 | } 764 | 765 | if (mouseaction(e, 1)) 766 | return; 767 | if (btn == Button1) 768 | mousesel(e, 1); 769 | } 770 | 771 | void 772 | bmotion(XEvent *e) 773 | { 774 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 775 | mousereport(e); 776 | return; 777 | } 778 | 779 | mousesel(e, 0); 780 | } 781 | 782 | void 783 | cresize(int width, int height) 784 | { 785 | int col, row; 786 | 787 | if (width != 0) 788 | win.w = width; 789 | if (height != 0) 790 | win.h = height; 791 | 792 | col = (win.w - 2 * borderpx) / win.cw; 793 | row = (win.h - 2 * borderpx) / win.ch; 794 | col = MAX(1, col); 795 | row = MAX(1, row); 796 | 797 | tresize(col, row); 798 | xresize(col, row); 799 | ttyresize(win.tw, win.th); 800 | } 801 | 802 | void 803 | xresize(int col, int row) 804 | { 805 | cairo_surface_t *surface; 806 | win.tw = col * win.cw; 807 | win.th = row * win.ch; 808 | 809 | XFreePixmap(xw.dpy, xw.buf); 810 | cairo_destroy(xw.draw); 811 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 812 | DefaultDepth(xw.dpy, xw.scr)); 813 | 814 | surface = cairo_xlib_surface_create(xw.dpy, xw.buf, xw.vis, win.w, win.h); 815 | xw.draw = cairo_create(surface); 816 | cairo_surface_destroy(surface); 817 | xclear(0, 0, win.w, win.h); 818 | 819 | /* resize to new width */ 820 | xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); 821 | } 822 | 823 | ushort 824 | sixd_to_16bit(int x) 825 | { 826 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; 827 | } 828 | 829 | int 830 | xloadcolor(int i, const char *name, Color *ncolor) 831 | { 832 | XColor screen, color; 833 | 834 | if (!name) { 835 | if (BETWEEN(i, 16, 255)) { /* 256 color */ 836 | if (i < 6*6*6+16) { /* same colors as xterm */ 837 | color.red = sixd_to_16bit( ((i-16)/36)%6 ); 838 | color.green = sixd_to_16bit( ((i-16)/6) %6 ); 839 | color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 840 | } else { /* greyscale */ 841 | color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 842 | color.green = color.blue = color.red; 843 | } 844 | ncolor->red = color.red; 845 | ncolor->green = color.green; 846 | ncolor->blue = color.blue; 847 | return XAllocColor(xw.dpy, xw.cmap, ncolor); 848 | } else { 849 | name = colorname[i]; 850 | } 851 | } 852 | 853 | return XAllocNamedColor(xw.dpy, xw.cmap, name, &screen, ncolor); 854 | } 855 | 856 | void 857 | xloadcols(void) 858 | { 859 | int i; 860 | static int loaded; 861 | Color *cp; 862 | 863 | if (loaded) { 864 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) 865 | XFreeColors(xw.dpy, xw.cmap, &cp->pixel, 1, 0); 866 | } else { 867 | dc.collen = MAX(LEN(colorname), 256); 868 | dc.col = xmalloc(dc.collen * sizeof(Color)); 869 | } 870 | 871 | for (i = 0; i < dc.collen; i++) 872 | if (!xloadcolor(i, NULL, &dc.col[i])) { 873 | if (colorname[i]) 874 | die("could not allocate color '%s'\n", colorname[i]); 875 | else 876 | die("could not allocate color %d\n", i); 877 | } 878 | loaded = 1; 879 | } 880 | 881 | int 882 | xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) 883 | { 884 | if (!BETWEEN(x, 0, dc.collen)) 885 | return 1; 886 | 887 | *r = dc.col[x].red >> 8; 888 | *g = dc.col[x].green >> 8; 889 | *b = dc.col[x].blue >> 8; 890 | 891 | return 0; 892 | } 893 | 894 | int 895 | xsetcolorname(int x, const char *name) 896 | { 897 | Color ncolor; 898 | 899 | if (!BETWEEN(x, 0, dc.collen)) 900 | return 1; 901 | 902 | if (!xloadcolor(x, name, &ncolor)) 903 | return 1; 904 | 905 | XFreeColors(xw.dpy, xw.cmap, &dc.col[x].pixel, 1, 0); 906 | dc.col[x] = ncolor; 907 | 908 | return 0; 909 | } 910 | 911 | /* 912 | * Absolute coordinates. 913 | */ 914 | void 915 | xclear(int x1, int y1, int x2, int y2) 916 | { 917 | xdrawrect(&dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 918 | x1, y1, x2-x1, y2-y1); 919 | } 920 | 921 | void 922 | xhints(void) 923 | { 924 | XClassHint class = {opt_name ? opt_name : "st", 925 | opt_class ? opt_class : "St"}; 926 | XWMHints wm = {.flags = InputHint, .input = 1}; 927 | XSizeHints *sizeh; 928 | 929 | sizeh = XAllocSizeHints(); 930 | 931 | sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 932 | sizeh->height = win.h; 933 | sizeh->width = win.w; 934 | sizeh->height_inc = win.ch; 935 | sizeh->width_inc = win.cw; 936 | sizeh->base_height = 2 * borderpx; 937 | sizeh->base_width = 2 * borderpx; 938 | sizeh->min_height = win.ch + 2 * borderpx; 939 | sizeh->min_width = win.cw + 2 * borderpx; 940 | if (xw.isfixed) { 941 | sizeh->flags |= PMaxSize; 942 | sizeh->min_width = sizeh->max_width = win.w; 943 | sizeh->min_height = sizeh->max_height = win.h; 944 | } 945 | if (xw.gm & (XValue|YValue)) { 946 | sizeh->flags |= USPosition | PWinGravity; 947 | sizeh->x = xw.l; 948 | sizeh->y = xw.t; 949 | sizeh->win_gravity = xgeommasktogravity(xw.gm); 950 | } 951 | 952 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 953 | &class); 954 | XFree(sizeh); 955 | } 956 | 957 | int 958 | xgeommasktogravity(int mask) 959 | { 960 | switch (mask & (XNegative|YNegative)) { 961 | case 0: 962 | return NorthWestGravity; 963 | case XNegative: 964 | return NorthEastGravity; 965 | case YNegative: 966 | return SouthWestGravity; 967 | } 968 | 969 | return SouthEastGravity; 970 | } 971 | 972 | int 973 | xloadfont(Font *f, FcPattern *pattern) 974 | { 975 | FcPattern *configured = NULL; 976 | FcPattern *match = NULL; 977 | FcResult result; 978 | FcChar8 *path = NULL; 979 | double fontsize; 980 | cairo_text_extents_t extents; 981 | int wantattr, haveattr; 982 | double dpi; 983 | 984 | /* 985 | * Manually configure so that we can use the configured pattern 986 | * for "missing glyph" lookups. 987 | */ 988 | configured = FcPatternDuplicate(pattern); 989 | if (!configured) 990 | return 1; 991 | 992 | /* get dpi and set to pattern */ 993 | dpi = (((double)DisplayHeight(xw.dpy, xw.scr) * 25.4) / 994 | (double)DisplayHeightMM(xw.dpy, xw.scr)); 995 | FcPatternAddDouble(configured, FC_DPI, dpi); 996 | 997 | FcConfigSubstitute(NULL, configured, FcMatchPattern); 998 | FcDefaultSubstitute(configured); 999 | 1000 | match = FcFontMatch(NULL, configured, &result); 1001 | if (!match) { 1002 | FcPatternDestroy(configured); 1003 | return 1; 1004 | } 1005 | 1006 | if (FcPatternGetString(match, FC_FILE, 0, &path) == FcResultNoMatch) { 1007 | FcPatternDestroy(configured); 1008 | FcPatternDestroy(match); 1009 | return 1; 1010 | } 1011 | 1012 | if (FT_New_Face(ftlib, (const char *)path, 0, &f->match.face)) { 1013 | FcPatternDestroy(configured); 1014 | FcPatternDestroy(match); 1015 | return 1; 1016 | } 1017 | 1018 | /* initialize font data */ 1019 | f->set = NULL; 1020 | f->pattern = configured; 1021 | f->match.pattern = match; 1022 | f->match.cairo = cairo_ft_font_face_create_for_ft_face( 1023 | f->match.face, FT_LOAD_COLOR); 1024 | 1025 | if ((FcPatternGetInteger(pattern, FC_SLANT, 0, &wantattr) == 1026 | FcResultMatch)) { 1027 | /* 1028 | * Check if fontconfig was unable to find a font with 1029 | * the appropriate slant but gave us one anyway. 1030 | * Try to mitigate. 1031 | */ 1032 | if ((FcPatternGetInteger(f->match.pattern, FC_SLANT, 0, 1033 | &haveattr) != FcResultMatch) || haveattr < wantattr) { 1034 | f->badslant = 1; 1035 | fputs("font slant does not match\n", stderr); 1036 | } else { 1037 | /* synthesize to oblique font */ 1038 | cairo_ft_font_face_set_synthesize(f->match.cairo, 1039 | CAIRO_FT_SYNTHESIZE_OBLIQUE); 1040 | } 1041 | } 1042 | 1043 | if ((FcPatternGetInteger(pattern, FC_WEIGHT, 0, &wantattr) == 1044 | FcResultMatch)) { 1045 | if ((FcPatternGetInteger(f->match.pattern, FC_WEIGHT, 0, 1046 | &haveattr) != FcResultMatch) || haveattr != wantattr) { 1047 | f->badweight = 1; 1048 | fputs("font weight does not match\n", stderr); 1049 | } else { 1050 | /* synthesize to bold font */ 1051 | cairo_ft_font_face_set_synthesize(f->match.cairo, 1052 | CAIRO_FT_SYNTHESIZE_BOLD); 1053 | } 1054 | } 1055 | 1056 | FcPatternGetDouble(f->match.pattern, FC_PIXEL_SIZE, 0, &fontsize); 1057 | cairo_set_font_size(xw.draw, fontsize); 1058 | cairo_set_font_face(xw.draw, f->match.cairo); 1059 | cairo_text_extents(xw.draw, ascii_printable, &extents); 1060 | 1061 | f->ascent = -extents.y_bearing; 1062 | f->descent = extents.height + extents.y_bearing; 1063 | f->lbearing = 0; 1064 | f->rbearing = f->match.face->max_advance_width; 1065 | f->height = extents.height; 1066 | f->width = DIVCEIL(extents.x_advance, strlen(ascii_printable)); 1067 | 1068 | return 0; 1069 | } 1070 | 1071 | void 1072 | xloadfonts(const char *fontstr, double fontsize) 1073 | { 1074 | FcPattern *pattern; 1075 | double fontval; 1076 | 1077 | if (!(pattern = FcNameParse((const FcChar8 *)fontstr))) 1078 | die("can't open font %s\n", fontstr); 1079 | 1080 | if (fontsize > 1) { 1081 | FcPatternDel(pattern, FC_PIXEL_SIZE); 1082 | FcPatternDel(pattern, FC_SIZE); 1083 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 1084 | usedfontsize = fontsize; 1085 | } else { 1086 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1087 | FcResultMatch) { 1088 | usedfontsize = fontval; 1089 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 1090 | FcResultMatch) { 1091 | usedfontsize = -1; 1092 | } else { 1093 | /* 1094 | * Default font size is 12, if none given. This is to 1095 | * have a known usedfontsize value. 1096 | */ 1097 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 1098 | usedfontsize = 12; 1099 | } 1100 | defaultfontsize = usedfontsize; 1101 | } 1102 | 1103 | if (xloadfont(&dc.font, pattern)) 1104 | die("can't open font %s\n", fontstr); 1105 | 1106 | if (usedfontsize < 0) { 1107 | FcPatternGetDouble(dc.font.match.pattern, 1108 | FC_PIXEL_SIZE, 0, &fontval); 1109 | usedfontsize = fontval; 1110 | if (fontsize == 0) 1111 | defaultfontsize = fontval; 1112 | } 1113 | 1114 | /* Setting character width and height. */ 1115 | win.cw = ceilf(dc.font.width * cwscale); 1116 | win.ch = ceilf(dc.font.height * chscale); 1117 | win.cyo = ceilf(dc.font.height * (chscale - 1) / 2); 1118 | 1119 | FcPatternDel(pattern, FC_SLANT); 1120 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1121 | if (xloadfont(&dc.ifont, pattern)) 1122 | die("can't open font %s\n", fontstr); 1123 | 1124 | FcPatternDel(pattern, FC_WEIGHT); 1125 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1126 | if (xloadfont(&dc.ibfont, pattern)) 1127 | die("can't open font %s\n", fontstr); 1128 | 1129 | FcPatternDel(pattern, FC_SLANT); 1130 | if (xloadfont(&dc.bfont, pattern)) 1131 | die("can't open font %s\n", fontstr); 1132 | 1133 | FcPatternDestroy(pattern); 1134 | } 1135 | 1136 | void 1137 | xunloadmatchfont(MatchFont match) 1138 | { 1139 | FT_Done_Face(match.face); 1140 | cairo_font_face_destroy(match.cairo); 1141 | FcPatternDestroy(match.pattern); 1142 | } 1143 | 1144 | void 1145 | xunloadfont(Font *f) 1146 | { 1147 | xunloadmatchfont(f->match); 1148 | FcPatternDestroy(f->pattern); 1149 | if (f->set) 1150 | FcFontSetDestroy(f->set); 1151 | } 1152 | 1153 | void 1154 | xunloadfonts(void) 1155 | { 1156 | /* Free the loaded fonts in the font cache. */ 1157 | while (frclen > 0) 1158 | xunloadmatchfont(frc[--frclen].font); 1159 | 1160 | xunloadfont(&dc.font); 1161 | xunloadfont(&dc.bfont); 1162 | xunloadfont(&dc.ifont); 1163 | xunloadfont(&dc.ibfont); 1164 | } 1165 | 1166 | int 1167 | ximopen(Display *dpy) 1168 | { 1169 | XIMPreeditAttrs *attrs; 1170 | XIMStyle ximstyle; 1171 | XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; 1172 | XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; 1173 | 1174 | if (!(xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL))) 1175 | return 0; 1176 | 1177 | if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) 1178 | fprintf(stderr, "XSetIMValues: " 1179 | "Could not set XNDestroyCallback.\n"); 1180 | 1181 | xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, 1182 | NULL); 1183 | ximstyle = ximpickupstyle(xw.ime.xim); 1184 | attrs = ximcreatepreeditattrs(dpy, ximstyle); 1185 | xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, ximstyle, 1186 | XNClientWindow, xw.win, 1187 | XNFocusWindow, xw.win, 1188 | XNDestroyCallback, &icdestroy, 1189 | attrs ? XNPreeditAttributes : NULL, 1190 | attrs ? attrs->list : NULL, 1191 | NULL); 1192 | if (attrs) 1193 | ximdestroypreeditattrs(dpy, attrs); 1194 | if (!xw.ime.xic) 1195 | fprintf(stderr, "XCreateIC: Could not create input context.\n"); 1196 | 1197 | return 1; 1198 | } 1199 | 1200 | XIMStyle 1201 | ximpickupstyle(XIM xim) 1202 | { 1203 | /* default is root style */ 1204 | XIMStyle style = (XIMPreeditNothing | XIMStatusNothing); 1205 | XIMStyles *styles; 1206 | int i, j; 1207 | int len = sizeof(ximstyles) / sizeof(XIMStyle); 1208 | 1209 | XGetIMValues(xim, XNQueryInputStyle, &styles, NULL); 1210 | for (i = 0; i < len; i++) 1211 | for (j = 0; j < styles->count_styles; j++) 1212 | if (ximstyles[i] == styles->supported_styles[j]) 1213 | goto LOOP_END; 1214 | LOOP_END: 1215 | if (i < styles->count_styles) 1216 | style = ximstyles[i]; 1217 | XFree(styles); 1218 | return style; 1219 | } 1220 | 1221 | XIMPreeditAttrs * 1222 | ximcreatepreeditattrs(Display *dpy, XIMStyle ximstyle) 1223 | { 1224 | char pat[32]; 1225 | char **missingcharlist; 1226 | int nummissingcharlist; 1227 | char *defstring; 1228 | XPoint spot = { .x = 0, .y = 0 }; 1229 | XIMPreeditAttrs *attrs; 1230 | 1231 | if (XIMPreeditPosition & ximstyle) { 1232 | attrs = calloc(1, sizeof(XIMPreeditAttrs)); 1233 | // '-1' for underline. 1234 | sprintf(pat, "-*-*-*-R-*-*-%d-*-*-*-*-*-*,*", dc.font.height-1); 1235 | attrs->fontset = XCreateFontSet(dpy, pat, &missingcharlist, 1236 | &nummissingcharlist, &defstring); 1237 | if (missingcharlist) 1238 | XFreeStringList(missingcharlist); 1239 | if (!attrs->fontset) 1240 | die("XCreateFontset failed."); 1241 | attrs->list = XVaCreateNestedList(0, XNFontSet, attrs->fontset, 1242 | XNSpotLocation, &spot, 1243 | XNForeground, dc.col[defaultfg].pixel, 1244 | XNBackground, dc.col[defaultbg].pixel, 1245 | NULL); 1246 | return attrs; 1247 | } 1248 | 1249 | return NULL; 1250 | } 1251 | 1252 | void 1253 | ximdestroypreeditattrs(Display *dpy, XIMPreeditAttrs *attrs) 1254 | { 1255 | XFree(attrs->list); 1256 | XFreeFontSet(dpy, attrs->fontset); 1257 | free(attrs); 1258 | } 1259 | 1260 | void 1261 | ximinstantiate(Display *dpy, XPointer client, XPointer call) 1262 | { 1263 | if (ximopen(dpy)) 1264 | XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1265 | ximinstantiate, NULL); 1266 | } 1267 | 1268 | void 1269 | ximdestroy(XIM xim, XPointer client, XPointer call) 1270 | { 1271 | xw.ime.xim = NULL; 1272 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1273 | ximinstantiate, NULL); 1274 | XFree(xw.ime.spotlist); 1275 | } 1276 | 1277 | int 1278 | xicdestroy(XIC xim, XPointer client, XPointer call) 1279 | { 1280 | xw.ime.xic = NULL; 1281 | return 1; 1282 | } 1283 | 1284 | void 1285 | xinit(int cols, int rows) 1286 | { 1287 | XGCValues gcvalues; 1288 | Cursor cursor; 1289 | Window parent; 1290 | pid_t thispid = getpid(); 1291 | XColor xmousefg, xmousebg; 1292 | cairo_surface_t *surface; 1293 | 1294 | if (!(xw.dpy = XOpenDisplay(NULL))) 1295 | die("can't open display\n"); 1296 | xw.scr = XDefaultScreen(xw.dpy); 1297 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); 1298 | 1299 | configinit(xw.dpy); 1300 | FT_Init_FreeType(&ftlib); 1301 | 1302 | /* font */ 1303 | if (!FcInit()) 1304 | die("could not init fontconfig.\n"); 1305 | 1306 | /* colors */ 1307 | xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 1308 | xloadcols(); 1309 | 1310 | /* adjust fixed window geometry */ 1311 | win.w = 2 * borderpx + cols * win.cw; 1312 | win.h = 2 * borderpx + rows * win.ch; 1313 | if (xw.gm & XNegative) 1314 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 1315 | if (xw.gm & YNegative) 1316 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 1317 | 1318 | /* Events */ 1319 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; 1320 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; 1321 | xw.attrs.bit_gravity = NorthWestGravity; 1322 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask 1323 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask 1324 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 1325 | xw.attrs.colormap = xw.cmap; 1326 | 1327 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) 1328 | parent = XRootWindow(xw.dpy, xw.scr); 1329 | xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, 1330 | win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, 1331 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 1332 | | CWEventMask | CWColormap, &xw.attrs); 1333 | 1334 | memset(&gcvalues, 0, sizeof(gcvalues)); 1335 | gcvalues.graphics_exposures = False; 1336 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 1337 | &gcvalues); 1338 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 1339 | DefaultDepth(xw.dpy, xw.scr)); 1340 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 1341 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 1342 | 1343 | /* font spec buffer */ 1344 | xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); 1345 | 1346 | /* cairo rendering context */ 1347 | surface = cairo_xlib_surface_create(xw.dpy, xw.buf, xw.vis, win.w, win.h); 1348 | xw.draw = cairo_create(surface); 1349 | cairo_surface_destroy(surface); 1350 | 1351 | usedfont = (opt_font == NULL)? font : opt_font; 1352 | xloadfonts(usedfont, 0); 1353 | 1354 | /* input methods */ 1355 | if (!ximopen(xw.dpy)) { 1356 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1357 | ximinstantiate, NULL); 1358 | } 1359 | 1360 | /* white cursor, black outline */ 1361 | cursor = XCreateFontCursor(xw.dpy, mouseshape); 1362 | XDefineCursor(xw.dpy, xw.win, cursor); 1363 | 1364 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 1365 | xmousefg.red = 0xffff; 1366 | xmousefg.green = 0xffff; 1367 | xmousefg.blue = 0xffff; 1368 | } 1369 | 1370 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 1371 | xmousebg.red = 0x0000; 1372 | xmousebg.green = 0x0000; 1373 | xmousebg.blue = 0x0000; 1374 | } 1375 | 1376 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 1377 | 1378 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 1379 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 1380 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 1381 | xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); 1382 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 1383 | 1384 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 1385 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 1386 | PropModeReplace, (uchar *)&thispid, 1); 1387 | 1388 | win.mode = MODE_NUMLOCK; 1389 | resettitle(); 1390 | xhints(); 1391 | XMapWindow(xw.dpy, xw.win); 1392 | XSync(xw.dpy, False); 1393 | 1394 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); 1395 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); 1396 | xsel.primary = NULL; 1397 | xsel.clipboard = NULL; 1398 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 1399 | if (xsel.xtarget == None) 1400 | xsel.xtarget = XA_STRING; 1401 | } 1402 | 1403 | 1404 | FT_UInt 1405 | xselectfont(Rune rune, int frcflags, MatchFont **ret) 1406 | { 1407 | FT_UInt glyphidx; 1408 | Font *font = &dc.font; 1409 | FcFontSet *fonts, *fcsets[] = { NULL }; 1410 | FcCharSet *fccharset; 1411 | FcPattern *fcpattern; 1412 | FcResult fcres; 1413 | FT_Face face; 1414 | FcChar8 *path; 1415 | int i, f; 1416 | 1417 | /* Lookup character index with default font. */ 1418 | glyphidx = FT_Get_Char_Index(font->match.face, rune); 1419 | if (glyphidx) { 1420 | *ret = &font->match; 1421 | return glyphidx; 1422 | } 1423 | 1424 | /* Fallback on font cache, search the font cache for match. */ 1425 | for (f = 0; f < frclen; f++) { 1426 | glyphidx = FT_Get_Char_Index(frc[f].font.face, rune); 1427 | /* Everything correct. */ 1428 | if (glyphidx && frc[f].flags == frcflags) 1429 | break; 1430 | /* We got a default font for a not found glyph. */ 1431 | if (!glyphidx && frc[f].flags == frcflags 1432 | && frc[f].unicodep == rune) { 1433 | break; 1434 | } 1435 | } 1436 | 1437 | /* Nothing was found. Use fontconfig to find matching font. */ 1438 | if (f >= frclen) { 1439 | if (!font->set) 1440 | font->set = FcFontSort(0, font->pattern, 1, 0, &fcres); 1441 | fcsets[0] = font->set; 1442 | 1443 | /* 1444 | * Nothing was found in the cache. Now use 1445 | * some dozen of Fontconfig calls to get the 1446 | * font for one single character. 1447 | * 1448 | * cairo and fontconfig are design failures. 1449 | */ 1450 | fcpattern = FcPatternDuplicate(font->pattern); 1451 | fccharset = FcCharSetCreate(); 1452 | 1453 | FcCharSetAddChar(fccharset, rune); 1454 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 1455 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1456 | FcPatternAddBool(fcpattern, FC_COLOR, 1); 1457 | 1458 | FcConfigSubstitute(0, fcpattern, FcMatchPattern); 1459 | FcDefaultSubstitute(fcpattern); 1460 | 1461 | fonts = FcFontSort(NULL, fcpattern, 1, NULL, &fcres); 1462 | FcPatternDestroy(fcpattern); 1463 | FcCharSetDestroy(fccharset); 1464 | if (!fonts) { 1465 | *ret = &font->match; 1466 | return glyphidx; 1467 | } 1468 | 1469 | /* Allocate memory for the new cache entry. */ 1470 | if (frclen >= frccap) { 1471 | frccap += 16; 1472 | frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1473 | } 1474 | 1475 | /* veirfy matched font */ 1476 | for (i = 0; i < fonts->nfont; i++) { 1477 | fcpattern = fonts->fonts[i]; 1478 | FcPatternGetString(fcpattern, FC_FILE, 0, &path); 1479 | if (FT_New_Face(ftlib, (const char *)path, 0, &face)) 1480 | die("FT_New_Face failed seeking fallback font: %s\n", path); 1481 | if ((glyphidx = FT_Get_Char_Index(face, rune))) { 1482 | fcpattern = FcPatternDuplicate(fcpattern); 1483 | break; 1484 | } 1485 | FT_Done_Face(face); 1486 | face = NULL; 1487 | } 1488 | FcFontSetDestroy(fonts); 1489 | if (!face) { 1490 | *ret = &font->match; 1491 | return 0; 1492 | } 1493 | 1494 | frc[frclen].font.face = face; 1495 | frc[frclen].font.cairo = cairo_ft_font_face_create_for_ft_face( 1496 | frc[frclen].font.face, FT_LOAD_COLOR); 1497 | frc[frclen].font.pattern = fcpattern; 1498 | frc[frclen].flags = frcflags; 1499 | frc[frclen].unicodep = rune; 1500 | 1501 | f = frclen; 1502 | frclen++; 1503 | } 1504 | *ret = &frc[f].font; 1505 | return glyphidx; 1506 | } 1507 | 1508 | int 1509 | xmakeglyphfontspecs(GlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 1510 | { 1511 | float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 1512 | ushort mode, prevmode = USHRT_MAX; 1513 | Font *font = &dc.font; 1514 | int frcflags = FRC_NORMAL; 1515 | float runewidth = win.cw; 1516 | Rune rune; 1517 | FT_UInt glyphidx; 1518 | int i, f, numspecs = 0; 1519 | 1520 | for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i) { 1521 | /* Fetch rune and mode for current glyph. */ 1522 | rune = glyphs[i].u; 1523 | mode = glyphs[i].mode; 1524 | 1525 | /* Skip dummy wide-character spacing. */ 1526 | if (mode == ATTR_WDUMMY) 1527 | continue; 1528 | 1529 | /* Determine font for glyph if different from previous glyph. */ 1530 | if (prevmode != mode) { 1531 | prevmode = mode; 1532 | font = &dc.font; 1533 | frcflags = FRC_NORMAL; 1534 | runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 1535 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 1536 | font = &dc.ibfont; 1537 | frcflags = FRC_ITALICBOLD; 1538 | } else if (mode & ATTR_ITALIC) { 1539 | font = &dc.ifont; 1540 | frcflags = FRC_ITALIC; 1541 | } else if (mode & ATTR_BOLD) { 1542 | font = &dc.bfont; 1543 | frcflags = FRC_BOLD; 1544 | } 1545 | yp = winy + font->ascent + win.cyo; 1546 | } 1547 | 1548 | /* select font */ 1549 | glyphidx = FT_Get_Char_Index(font->match.face, rune); 1550 | if (!glyphidx) 1551 | glyphidx = xselectfont(rune, frcflags, &specs[numspecs].font); 1552 | else 1553 | specs[numspecs].font = &font->match; 1554 | if (glyphidx) { 1555 | specs[numspecs].glyph.index = glyphidx; 1556 | specs[numspecs].glyph.x = (short)xp; 1557 | specs[numspecs].glyph.y = (short)yp; 1558 | } 1559 | 1560 | xp += runewidth; 1561 | numspecs++; 1562 | } 1563 | 1564 | return numspecs; 1565 | } 1566 | 1567 | void 1568 | xdrawglyphfontspecs(const GlyphFontSpec *specs, Glyph base, int len, int x, int y) 1569 | { 1570 | 1571 | int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 1572 | int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 1573 | width = charlen * win.cw; 1574 | Color *fg, *bg, *temp, colfg, colbg; 1575 | XRectangle r; 1576 | 1577 | /* Fallback on color display for attributes not supported by the font */ 1578 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1579 | if (dc.ibfont.badslant || dc.ibfont.badweight) 1580 | base.fg = defaultattr; 1581 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1582 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1583 | base.fg = defaultattr; 1584 | } 1585 | 1586 | if (IS_TRUECOL(base.fg)) { 1587 | colfg.red = TRUERED(base.fg); 1588 | colfg.green = TRUEGREEN(base.fg); 1589 | colfg.blue = TRUEBLUE(base.fg); 1590 | XAllocColor(xw.dpy, xw.cmap, &colfg); 1591 | fg = &colfg; 1592 | } else { 1593 | fg = &dc.col[base.fg]; 1594 | } 1595 | 1596 | if (IS_TRUECOL(base.bg)) { 1597 | colbg.green = TRUEGREEN(base.bg); 1598 | colbg.red = TRUERED(base.bg); 1599 | colbg.blue = TRUEBLUE(base.bg); 1600 | XAllocColor(xw.dpy, xw.cmap, &colbg); 1601 | bg = &colbg; 1602 | } else { 1603 | bg = &dc.col[base.bg]; 1604 | } 1605 | 1606 | /* Change basic system colors [0-7] to bright system colors [8-15] */ 1607 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 1608 | fg = &dc.col[base.fg + 8]; 1609 | 1610 | if (IS_SET(MODE_REVERSE)) { 1611 | if (fg == &dc.col[defaultfg]) { 1612 | fg = &dc.col[defaultbg]; 1613 | } else { 1614 | colfg.red = ~fg->red; 1615 | colfg.green = ~fg->green; 1616 | colfg.blue = ~fg->blue; 1617 | XAllocColor(xw.dpy, xw.cmap, &colfg); 1618 | fg = &colfg; 1619 | } 1620 | 1621 | if (bg == &dc.col[defaultbg]) { 1622 | bg = &dc.col[defaultfg]; 1623 | } else { 1624 | colbg.red = ~bg->red; 1625 | colbg.green = ~bg->green; 1626 | colbg.blue = ~bg->blue; 1627 | XAllocColor(xw.dpy, xw.cmap, &colbg); 1628 | bg = &colbg; 1629 | } 1630 | } 1631 | 1632 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1633 | colfg.red = fg->red / 2; 1634 | colfg.green = fg->green / 2; 1635 | colfg.blue = fg->blue / 2; 1636 | XAllocColor(xw.dpy, xw.cmap, &colfg); 1637 | fg = &colfg; 1638 | } 1639 | 1640 | if (base.mode & ATTR_REVERSE) { 1641 | temp = fg; 1642 | fg = bg; 1643 | bg = temp; 1644 | } 1645 | 1646 | if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) 1647 | fg = bg; 1648 | 1649 | if (base.mode & ATTR_INVISIBLE) 1650 | fg = bg; 1651 | 1652 | /* Intelligent cleaning up of the borders. */ 1653 | if (x == 0) { 1654 | xclear(0, (y == 0)? 0 : winy, borderpx, 1655 | winy + win.ch + 1656 | ((winy + win.ch >= borderpx + win.th)? win.h : 0)); 1657 | } 1658 | if (winx + width >= borderpx + win.tw) { 1659 | xclear(winx + width, (y == 0)? 0 : winy, win.w, 1660 | ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); 1661 | } 1662 | if (y == 0) 1663 | xclear(winx, 0, winx + width, borderpx); 1664 | if (winy + win.ch >= borderpx + win.th) 1665 | xclear(winx, winy + win.ch, winx + width, win.h); 1666 | 1667 | /* Clean up the region we want to draw to. */ 1668 | xdrawrect(bg, winx, winy, width, win.ch); 1669 | 1670 | /* Render the glyphs. */ 1671 | xdrawglyphfontspec(fg, specs, len); 1672 | 1673 | /* Render underline and strikethrough. */ 1674 | if (base.mode & ATTR_UNDERLINE) 1675 | xdrawrect(fg, winx, winy + win.cyo + dc.font.ascent + 1, width, 1); 1676 | 1677 | if (base.mode & ATTR_STRUCK) 1678 | xdrawrect(fg, winx, winy + win.cyo + 2 * dc.font.ascent / 3, width, 1); 1679 | } 1680 | 1681 | void 1682 | xdrawglyphfontspec(XColor *fg, const GlyphFontSpec *specs, int len) 1683 | { 1684 | cairo_font_face_t *prev = NULL; 1685 | int i; 1686 | 1687 | cairo_set_font_size(xw.draw, defaultfontsize); 1688 | cairo_set_source_rgb(xw.draw, CONVCOL(fg->red), CONVCOL(fg->green), CONVCOL(fg->blue)); 1689 | for (i = 0; i < len; i++) { 1690 | if (prev != specs[i].font->cairo) { 1691 | prev = specs[i].font->cairo; 1692 | cairo_set_font_face(xw.draw, prev); 1693 | } 1694 | cairo_show_glyphs(xw.draw, (cairo_glyph_t *)&specs[i].glyph, 1); 1695 | } 1696 | } 1697 | 1698 | void 1699 | xdrawrect(XColor *col, int x, int y, int width, int height) 1700 | { 1701 | XSetForeground(xw.dpy, dc.gc, col->pixel); 1702 | XFillRectangle(xw.dpy, xw.buf, dc.gc, x, y, width, height); 1703 | } 1704 | 1705 | void 1706 | xdrawglyph(Glyph g, int x, int y) 1707 | { 1708 | int numspecs; 1709 | GlyphFontSpec spec; 1710 | 1711 | numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 1712 | xdrawglyphfontspecs(&spec, g, numspecs, x, y); 1713 | } 1714 | 1715 | void 1716 | xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 1717 | { 1718 | Color drawcol; 1719 | 1720 | /* remove the old cursor */ 1721 | if (selected(ox, oy)) 1722 | og.mode ^= ATTR_REVERSE; 1723 | xdrawglyph(og, ox, oy); 1724 | 1725 | if (IS_SET(MODE_HIDE)) 1726 | return; 1727 | 1728 | /* 1729 | * Select the right color for the right mode. 1730 | */ 1731 | g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; 1732 | 1733 | if (IS_SET(MODE_REVERSE)) { 1734 | g.mode |= ATTR_REVERSE; 1735 | g.bg = defaultfg; 1736 | if (selected(cx, cy)) { 1737 | drawcol = dc.col[defaultcs]; 1738 | g.fg = defaultrcs; 1739 | } else { 1740 | drawcol = dc.col[defaultrcs]; 1741 | g.fg = defaultcs; 1742 | } 1743 | } else { 1744 | if (selected(cx, cy)) { 1745 | g.fg = defaultfg; 1746 | g.bg = defaultrcs; 1747 | } else { 1748 | g.fg = defaultbg; 1749 | g.bg = defaultcs; 1750 | } 1751 | drawcol = dc.col[g.bg]; 1752 | } 1753 | 1754 | /* draw the new one */ 1755 | if (IS_SET(MODE_FOCUSED)) { 1756 | switch (win.cursor) { 1757 | case 7: /* st extension */ 1758 | g.u = 0x2603; /* snowman (U+2603) */ 1759 | /* FALLTHROUGH */ 1760 | case 0: /* Blinking Block */ 1761 | case 1: /* Blinking Block (Default) */ 1762 | case 2: /* Steady Block */ 1763 | xdrawglyph(g, cx, cy); 1764 | break; 1765 | case 3: /* Blinking Underline */ 1766 | case 4: /* Steady Underline */ 1767 | xdrawrect(&drawcol, borderpx + cx * win.cw, 1768 | borderpx + (cy + 1) * win.ch - cursorthickness, 1769 | win.cw, cursorthickness); 1770 | break; 1771 | case 5: /* Blinking bar */ 1772 | case 6: /* Steady bar */ 1773 | xdrawrect(&drawcol, borderpx + cx * win.cw, 1774 | borderpx + cy * win.ch, 1775 | cursorthickness, win.ch); 1776 | break; 1777 | } 1778 | } else { 1779 | xdrawrect(&drawcol, 1780 | borderpx + cx * win.cw, 1781 | borderpx + cy * win.ch, 1782 | win.cw - 1, 1); 1783 | xdrawrect(&drawcol, 1784 | borderpx + cx * win.cw, 1785 | borderpx + cy * win.ch, 1786 | 1, win.ch - 1); 1787 | xdrawrect(&drawcol, 1788 | borderpx + (cx + 1) * win.cw - 1, 1789 | borderpx + cy * win.ch, 1790 | 1, win.ch - 1); 1791 | xdrawrect(&drawcol, 1792 | borderpx + cx * win.cw, 1793 | borderpx + (cy + 1) * win.ch - 1, 1794 | win.cw, 1); 1795 | } 1796 | } 1797 | 1798 | void 1799 | xsetenv(void) 1800 | { 1801 | char buf[sizeof(long) * 8 + 1]; 1802 | 1803 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1804 | setenv("WINDOWID", buf, 1); 1805 | } 1806 | 1807 | void 1808 | xseticontitle(char *p) 1809 | { 1810 | XTextProperty prop; 1811 | DEFAULT(p, opt_title); 1812 | 1813 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1814 | &prop) != Success) 1815 | return; 1816 | XSetWMIconName(xw.dpy, xw.win, &prop); 1817 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); 1818 | XFree(prop.value); 1819 | } 1820 | 1821 | void 1822 | xsettitle(char *p) 1823 | { 1824 | XTextProperty prop; 1825 | DEFAULT(p, opt_title); 1826 | 1827 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1828 | &prop) != Success) 1829 | return; 1830 | XSetWMName(xw.dpy, xw.win, &prop); 1831 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1832 | XFree(prop.value); 1833 | } 1834 | 1835 | int 1836 | xstartdraw(void) 1837 | { 1838 | return IS_SET(MODE_VISIBLE); 1839 | } 1840 | 1841 | void 1842 | xdrawline(Line line, int x1, int y1, int x2) 1843 | { 1844 | int i, x, ox, numspecs; 1845 | Glyph base, new; 1846 | GlyphFontSpec *specs = xw.specbuf; 1847 | 1848 | numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 1849 | 1850 | i = ox = 0; 1851 | for (x = x1; x < x2 && i < numspecs; x++) { 1852 | new = line[x]; 1853 | if (new.mode == ATTR_WDUMMY) 1854 | continue; 1855 | if (selected(x, y1)) 1856 | new.mode ^= ATTR_REVERSE; 1857 | if (i > 0 && ATTRCMP(base, new)) { 1858 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1859 | specs += i; 1860 | numspecs -= i; 1861 | i = 0; 1862 | } 1863 | if (i == 0) { 1864 | ox = x; 1865 | base = new; 1866 | } 1867 | i++; 1868 | } 1869 | if (i > 0) 1870 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1871 | } 1872 | 1873 | void 1874 | xfinishdraw(void) 1875 | { 1876 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 1877 | win.h, 0, 0); 1878 | XSetForeground(xw.dpy, dc.gc, 1879 | dc.col[IS_SET(MODE_REVERSE)? 1880 | defaultfg : defaultbg].pixel); 1881 | } 1882 | 1883 | void 1884 | xximspot(int x, int y) 1885 | { 1886 | if (xw.ime.xic == NULL) 1887 | return; 1888 | /* '-1' for underline. */ 1889 | XPoint spot = { borderpx + x * win.cw, (y + 1) * win.ch - 1 }; 1890 | XVaNestedList attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); 1891 | 1892 | xw.ime.spot.x = borderpx + x * win.cw; 1893 | xw.ime.spot.y = (y + 1) * win.ch - 1; 1894 | 1895 | XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); 1896 | XFree(attr); 1897 | } 1898 | 1899 | void 1900 | expose(XEvent *ev) 1901 | { 1902 | redraw(); 1903 | } 1904 | 1905 | void 1906 | visibility(XEvent *ev) 1907 | { 1908 | XVisibilityEvent *e = &ev->xvisibility; 1909 | 1910 | MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); 1911 | } 1912 | 1913 | void 1914 | unmap(XEvent *ev) 1915 | { 1916 | win.mode &= ~MODE_VISIBLE; 1917 | } 1918 | 1919 | void 1920 | xsetpointermotion(int set) 1921 | { 1922 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1923 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1924 | } 1925 | 1926 | void 1927 | xsetmode(int set, unsigned int flags) 1928 | { 1929 | int mode = win.mode; 1930 | MODBIT(win.mode, set, flags); 1931 | if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) 1932 | redraw(); 1933 | } 1934 | 1935 | int 1936 | xsetcursor(int cursor) 1937 | { 1938 | if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ 1939 | return 1; 1940 | win.cursor = cursor; 1941 | return 0; 1942 | } 1943 | 1944 | void 1945 | xseturgency(int add) 1946 | { 1947 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1948 | 1949 | MODBIT(h->flags, add, XUrgencyHint); 1950 | XSetWMHints(xw.dpy, xw.win, h); 1951 | XFree(h); 1952 | } 1953 | 1954 | void 1955 | xbell(void) 1956 | { 1957 | if (!(IS_SET(MODE_FOCUSED))) 1958 | xseturgency(1); 1959 | if (bellvolume) 1960 | XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 1961 | } 1962 | 1963 | void 1964 | focus(XEvent *ev) 1965 | { 1966 | XFocusChangeEvent *e = &ev->xfocus; 1967 | 1968 | if (e->mode == NotifyGrab) 1969 | return; 1970 | 1971 | if (ev->type == FocusIn) { 1972 | if (xw.ime.xic) 1973 | XSetICFocus(xw.ime.xic); 1974 | win.mode |= MODE_FOCUSED; 1975 | xseturgency(0); 1976 | if (IS_SET(MODE_FOCUS)) 1977 | ttywrite("\033[I", 3, 0); 1978 | } else { 1979 | if (xw.ime.xic) 1980 | XUnsetICFocus(xw.ime.xic); 1981 | win.mode &= ~MODE_FOCUSED; 1982 | if (IS_SET(MODE_FOCUS)) 1983 | ttywrite("\033[O", 3, 0); 1984 | } 1985 | } 1986 | 1987 | int 1988 | match(uint mask, uint state) 1989 | { 1990 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 1991 | } 1992 | 1993 | char* 1994 | kmap(KeySym k, uint state) 1995 | { 1996 | Key *kp; 1997 | int i; 1998 | 1999 | /* Check for mapped keys out of X11 function keys. */ 2000 | for (i = 0; i < LEN(mappedkeys); i++) { 2001 | if (mappedkeys[i] == k) 2002 | break; 2003 | } 2004 | if (i == LEN(mappedkeys)) { 2005 | if ((k & 0xFFFF) < 0xFD00) 2006 | return NULL; 2007 | } 2008 | 2009 | for (kp = key; kp < key + LEN(key); kp++) { 2010 | if (kp->k != k) 2011 | continue; 2012 | 2013 | if (!match(kp->mask, state)) 2014 | continue; 2015 | 2016 | if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 2017 | continue; 2018 | if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) 2019 | continue; 2020 | 2021 | if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 2022 | continue; 2023 | 2024 | return kp->s; 2025 | } 2026 | 2027 | return NULL; 2028 | } 2029 | 2030 | void 2031 | kpress(XEvent *ev) 2032 | { 2033 | XKeyEvent *e = &ev->xkey; 2034 | KeySym ksym = NoSymbol; 2035 | char buf[64], *customkey; 2036 | int len; 2037 | Rune c; 2038 | Status status; 2039 | Shortcut *bp; 2040 | 2041 | if (IS_SET(MODE_KBDLOCK)) 2042 | return; 2043 | 2044 | if (xw.ime.xic) { 2045 | len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 2046 | if (status == XBufferOverflow) 2047 | return; 2048 | } else { 2049 | len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 2050 | } 2051 | /* 1. shortcuts */ 2052 | for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 2053 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 2054 | bp->func(&(bp->arg)); 2055 | return; 2056 | } 2057 | } 2058 | 2059 | /* 2. custom keys from config.h */ 2060 | if ((customkey = kmap(ksym, e->state))) { 2061 | ttywrite(customkey, strlen(customkey), 1); 2062 | return; 2063 | } 2064 | 2065 | /* 3. composed string from input method */ 2066 | if (len == 0) 2067 | return; 2068 | if (len == 1 && e->state & Mod1Mask) { 2069 | if (IS_SET(MODE_8BIT)) { 2070 | if (*buf < 0177) { 2071 | c = *buf | 0x80; 2072 | len = utf8encode(c, buf); 2073 | } 2074 | } else { 2075 | buf[1] = buf[0]; 2076 | buf[0] = '\033'; 2077 | len = 2; 2078 | } 2079 | } 2080 | ttywrite(buf, len, 1); 2081 | } 2082 | 2083 | void 2084 | cmessage(XEvent *e) 2085 | { 2086 | /* 2087 | * See xembed specs 2088 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 2089 | */ 2090 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 2091 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 2092 | win.mode |= MODE_FOCUSED; 2093 | xseturgency(0); 2094 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 2095 | win.mode &= ~MODE_FOCUSED; 2096 | } 2097 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 2098 | ttyhangup(); 2099 | exit(0); 2100 | } 2101 | } 2102 | 2103 | void 2104 | resize(XEvent *e) 2105 | { 2106 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 2107 | return; 2108 | 2109 | cresize(e->xconfigure.width, e->xconfigure.height); 2110 | } 2111 | 2112 | void 2113 | run(void) 2114 | { 2115 | XEvent ev; 2116 | int w = win.w, h = win.h; 2117 | fd_set rfd; 2118 | int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; 2119 | struct timespec seltv, *tv, now, lastblink, trigger; 2120 | double timeout; 2121 | 2122 | /* Waiting for window mapping */ 2123 | do { 2124 | XNextEvent(xw.dpy, &ev); 2125 | /* 2126 | * This XFilterEvent call is required because of XOpenIM. It 2127 | * does filter out the key event and some client message for 2128 | * the input method too. 2129 | */ 2130 | if (XFilterEvent(&ev, None)) 2131 | continue; 2132 | if (ev.type == ConfigureNotify) { 2133 | w = ev.xconfigure.width; 2134 | h = ev.xconfigure.height; 2135 | } 2136 | } while (ev.type != MapNotify); 2137 | 2138 | ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 2139 | cresize(w, h); 2140 | 2141 | for (timeout = -1, drawing = 0, lastblink = (struct timespec){0}; canloop;) { 2142 | FD_ZERO(&rfd); 2143 | FD_SET(ttyfd, &rfd); 2144 | FD_SET(xfd, &rfd); 2145 | 2146 | if (XPending(xw.dpy)) 2147 | timeout = 0; /* existing events might not set xfd */ 2148 | 2149 | seltv.tv_sec = timeout / 1E3; 2150 | seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); 2151 | tv = timeout >= 0 ? &seltv : NULL; 2152 | 2153 | if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 2154 | if (errno == EBADF) 2155 | break; 2156 | if (errno == EINTR) 2157 | continue; 2158 | die("select failed: %s\n", strerror(errno)); 2159 | } 2160 | clock_gettime(CLOCK_MONOTONIC, &now); 2161 | 2162 | if (FD_ISSET(ttyfd, &rfd)) 2163 | ttyread(); 2164 | 2165 | xev = 0; 2166 | while (XPending(xw.dpy)) { 2167 | xev = 1; 2168 | XNextEvent(xw.dpy, &ev); 2169 | if (XFilterEvent(&ev, None)) 2170 | continue; 2171 | if (handler[ev.type]) 2172 | (handler[ev.type])(&ev); 2173 | } 2174 | 2175 | /* 2176 | * To reduce flicker and tearing, when new content or event 2177 | * triggers drawing, we first wait a bit to ensure we got 2178 | * everything, and if nothing new arrives - we draw. 2179 | * We start with trying to wait minlatency ms. If more content 2180 | * arrives sooner, we retry with shorter and shorter periods, 2181 | * and eventually draw even without idle after maxlatency ms. 2182 | * Typically this results in low latency while interacting, 2183 | * maximum latency intervals during `cat huge.txt`, and perfect 2184 | * sync with periodic updates from animations/key-repeats/etc. 2185 | */ 2186 | if (FD_ISSET(ttyfd, &rfd) || xev) { 2187 | if (!drawing) { 2188 | trigger = now; 2189 | drawing = 1; 2190 | } 2191 | timeout = (maxlatency - TIMEDIFF(now, trigger)) \ 2192 | / maxlatency * minlatency; 2193 | if (timeout > 0) 2194 | continue; /* we have time, try to find idle */ 2195 | } 2196 | 2197 | /* idle detected or maxlatency exhausted -> draw */ 2198 | timeout = -1; 2199 | if (blinktimeout && tattrset(ATTR_BLINK)) { 2200 | timeout = blinktimeout - TIMEDIFF(now, lastblink); 2201 | if (timeout <= 0) { 2202 | if (-timeout > blinktimeout) /* start visible */ 2203 | win.mode |= MODE_BLINK; 2204 | win.mode ^= MODE_BLINK; 2205 | tsetdirtattr(ATTR_BLINK); 2206 | lastblink = now; 2207 | timeout = blinktimeout; 2208 | } 2209 | } 2210 | 2211 | draw(); 2212 | XFlush(xw.dpy); 2213 | drawing = 0; 2214 | } 2215 | } 2216 | 2217 | int 2218 | resourceload(XrmDatabase db, char *name, enum resource_type rtype, void *dst) 2219 | { 2220 | char **sdst = dst; 2221 | int *idst = dst; 2222 | float *fdst = dst; 2223 | 2224 | char fullname[256]; 2225 | char fullclass[256]; 2226 | char *type; 2227 | XrmValue ret; 2228 | 2229 | snprintf(fullname, sizeof(fullname), "%s.%s", 2230 | opt_name ? opt_name : "st", name); 2231 | snprintf(fullclass, sizeof(fullclass), "%s.%s", 2232 | opt_class ? opt_class : "St", name); 2233 | fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; 2234 | 2235 | XrmGetResource(db, fullname, fullclass, &type, &ret); 2236 | if (ret.addr == NULL || strncmp("String", type, 64)) 2237 | return 1; 2238 | 2239 | switch (rtype) { 2240 | case STRING: 2241 | *sdst = ret.addr; 2242 | break; 2243 | case INTEGER: 2244 | *idst = strtoul(ret.addr, NULL, 10); 2245 | break; 2246 | case FLOAT: 2247 | *fdst = strtof(ret.addr, NULL); 2248 | break; 2249 | } 2250 | return 0; 2251 | } 2252 | 2253 | void 2254 | configinit(Display *dpy) 2255 | { 2256 | char *resm; 2257 | ResourcePref *p; 2258 | 2259 | XrmInitialize(); 2260 | resm = XResourceManagerString(dpy); 2261 | if (!resm) 2262 | return; 2263 | 2264 | db = XrmGetStringDatabase(resm); 2265 | for (p = resources; p < resources + LEN(resources); p++) 2266 | resourceload(db, p->name, p->type, p->dst); 2267 | } 2268 | 2269 | void 2270 | cleanup() 2271 | { 2272 | XColor *cp; 2273 | xunloadfonts(); 2274 | XDestroyIC(xw.ime.xic); 2275 | XCloseIM(xw.ime.xim); 2276 | 2277 | free(dc.col); 2278 | free(xw.specbuf); 2279 | free(frc); 2280 | 2281 | XFreePixmap(xw.dpy, xw.buf); 2282 | XDestroyWindow(xw.dpy, xw.win); 2283 | XrmDestroyDatabase(db); 2284 | XCloseDisplay(xw.dpy); 2285 | } 2286 | 2287 | void 2288 | stoploop() 2289 | { 2290 | canloop = 0; 2291 | } 2292 | 2293 | void 2294 | usage(void) 2295 | { 2296 | die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2297 | " [-n name] [-o file]\n" 2298 | " [-T title] [-t title] [-w windowid]" 2299 | " [[-e] command [args ...]]\n" 2300 | " %s [-aiv] [-c class] [-f font] [-g geometry]" 2301 | " [-n name] [-o file]\n" 2302 | " [-T title] [-t title] [-w windowid] -l line" 2303 | " [stty_args ...]\n", argv0, argv0); 2304 | } 2305 | 2306 | int 2307 | main(int argc, char *argv[]) 2308 | { 2309 | xw.l = xw.t = 0; 2310 | xw.isfixed = False; 2311 | xsetcursor(cursorshape); 2312 | 2313 | ARGBEGIN { 2314 | case 'a': 2315 | allowaltscreen = 0; 2316 | break; 2317 | case 'c': 2318 | opt_class = EARGF(usage()); 2319 | break; 2320 | case 'e': 2321 | if (argc > 0) 2322 | --argc, ++argv; 2323 | goto run; 2324 | case 'f': 2325 | opt_font = EARGF(usage()); 2326 | break; 2327 | case 'g': 2328 | xw.gm = XParseGeometry(EARGF(usage()), 2329 | &xw.l, &xw.t, &cols, &rows); 2330 | break; 2331 | case 'i': 2332 | xw.isfixed = 1; 2333 | break; 2334 | case 'o': 2335 | opt_io = EARGF(usage()); 2336 | break; 2337 | case 'l': 2338 | opt_line = EARGF(usage()); 2339 | break; 2340 | case 'n': 2341 | opt_name = EARGF(usage()); 2342 | break; 2343 | case 't': 2344 | case 'T': 2345 | opt_title = EARGF(usage()); 2346 | break; 2347 | case 'w': 2348 | opt_embed = EARGF(usage()); 2349 | break; 2350 | case 'v': 2351 | die("%s " VERSION "\n", argv0); 2352 | break; 2353 | default: 2354 | usage(); 2355 | } ARGEND; 2356 | 2357 | run: 2358 | if (argc > 0) /* eat all remaining arguments */ 2359 | opt_cmd = argv; 2360 | 2361 | if (!opt_title) 2362 | opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; 2363 | 2364 | setlocale(LC_CTYPE, ""); 2365 | XSetLocaleModifiers(""); 2366 | cols = MAX(cols, 1); 2367 | rows = MAX(rows, 1); 2368 | addcleanfunc(stoploop); 2369 | tnew(cols, rows); 2370 | xinit(cols, rows); 2371 | xsetenv(); 2372 | selinit(); 2373 | run(); 2374 | cleanup(); 2375 | 2376 | return 0; 2377 | } 2378 | --------------------------------------------------------------------------------