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