diff --git a/.gitignore b/.gitignore index 58436c8..c5cc104 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -compile_commands.json -boxdraw.o -hb.o +*.o st -st.o -x.o diff --git a/.woodpecker.yml b/.woodpecker.yml index d79881a..f6602cc 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -6,7 +6,7 @@ steps: build: image: gcc environment: - - BUILD_DEPS=libx11-dev libxft-dev libfreetype6-dev libfontconfig1-dev libharfbuzz-dev + - BUILD_DEPS=libx11-dev libxft-dev libfreetype6-dev libfontconfig1-dev libharfbuzz-dev libxcursor-dev commands: | apt-get update apt-get install -y $${BUILD_DEPS} diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..b4d0b8a --- /dev/null +++ b/FAQ @@ -0,0 +1,253 @@ +## Why does st not handle utmp entries? + +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. + + +## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! + +It means that st doesn’t have any terminfo entry on your system. Chances are +you did not `make install`. If you just want to test it without installing it, +you can manually run `tic -sx st.info`. + + +## Nothing works, and nothing is said about an unknown terminal! + +* Some programs just assume they’re running in xterm i.e. they don’t rely on + terminfo. What you see is the current state of the “xterm compliance”. +* Some programs don’t complain about the lacking st description and default to + another terminal. In that case see the question about terminfo. + + +## How do I scroll back up? + +* Using a terminal multiplexer. + * `st -e tmux` using C-b [ + * `st -e screen` using C-a ESC +* Using the excellent tool of [scroll](https://git.suckless.org/scroll/). +* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). + + +## I would like to have utmp and/or scroll functionality by default + +You can add the absolute path of both programs in your config.h file. You only +have to modify the value of utmp and scroll variables. + + +## Why doesn't the Del key work in some programs? + +Taken from the terminfo manpage: + + If the terminal has a keypad that transmits codes when the keys + are pressed, this information can be given. Note that it is not + possible to handle terminals where the keypad only works in + local (this applies, for example, to the unshifted HP 2621 keys). + If the keypad can be set to transmit or not transmit, give these + codes as smkx and rmkx. Otherwise the keypad is assumed to + always transmit. + +In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +: + + It should be noted that the O / [ confusion can occur with other keys + such as Home and End. Some systems let you query the key sequences + sent by these keys from the system's terminal database, terminfo. + Unfortunately, the key sequences given there typically apply to the + mode that is not the one zsh uses by default (it's the "application" + mode rather than the "raw" mode). Explaining the use of terminfo is + outside of the scope of this FAQ, but if you wish to use the key + sequences given there you can tell the line editor to turn on + "application" mode when it starts and turn it off when it stops: + + function zle-line-init () { echoti smkx } + function zle-line-finish () { echoti rmkx } + zle -N zle-line-init + zle -N zle-line-finish + +Putting these lines into your .zshrc will fix the problems. + + +## How can I use meta in 8bit mode? + +St supports meta in 8bit mode, but the default terminfo entry doesn't +use this capability. If you want it, you have to use the 'st-meta' value +in TERM. + + +## I cannot compile st in OpenBSD + +OpenBSD lacks librt, despite it being mandatory in POSIX +. +If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and +st will compile without any loss of functionality, because all the functions are +included in libc on this platform. + + +## The Backspace Case + +St is emulating the Linux way of handling backspace being delete and delete being +backspace. + +This is an issue that was discussed in suckless mailing list +. Here is why some old grumpy +terminal users wants its backspace to be how he feels it: + + Well, I am going to comment why I want to change the behaviour + of this key. When ASCII was defined in 1968, communication + with computers was done using punched cards, or hardcopy + terminals (basically a typewriter machine connected with the + computer using a serial port). ASCII defines DELETE as 7F, + because, in punched-card terms, it means all the holes of the + card punched; it is thus a kind of 'physical delete'. In the + same way, the BACKSPACE key was a non-destructive backspace, + as on a typewriter. So, if you wanted to delete a character, + you had to BACKSPACE and then DELETE. Another use of BACKSPACE + was to type accented characters, for example 'a BACKSPACE `'. + The VT100 had no BACKSPACE key; it was generated using the + CONTROL key as another control character (CONTROL key sets to + 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code + 0x08)), but it had a DELETE key in a similar position where + the BACKSPACE key is located today on common PC keyboards. + All the terminal emulators emulated the difference between + these keys correctly: the backspace key generated a BACKSPACE + (^H) and delete key generated a DELETE (^?). + + But a problem arose when Linus Torvalds wrote Linux. Unlike + earlier terminals, the Linux virtual terminal (the terminal + emulator integrated in the kernel) returned a DELETE when + backspace was pressed, due to the VT100 having a DELETE key in + the same position. This created a lot of problems (see [1] + and [2]). Since Linux has become the king, a lot of terminal + emulators today generate a DELETE when the backspace key is + pressed in order to avoid problems with Linux. The result is + that the only way of generating a BACKSPACE on these systems + is by using CONTROL + H. (I also think that emacs had an + important point here because the CONTROL + H prefix is used + in emacs in some commands (help commands).) + + From point of view of the kernel, you can change the key + for deleting a previous character with stty erase. When you + connect a real terminal into a machine you describe the type + of terminal, so getty configures the correct value of stty + erase for this terminal. In the case of terminal emulators, + however, you don't have any getty that can set the correct + value of stty erase, so you always get the default value. + For this reason, it is necessary to add 'stty erase ^H' to your + profile if you have changed the value of the backspace key. + Of course, another solution is for st itself to modify the + value of stty erase. I usually have the inverse problem: + when I connect to non-Unix machines, I have to press CONTROL + + h to get a BACKSPACE. The inverse problem occurs when a user + connects to my Unix machines from a different system with a + correct backspace key. + + [1] http://www.ibb.net/~anne/keyboard.html + [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html + + +## But I really want the old grumpy behaviour of my terminal + +Apply [1]. + +[1] https://st.suckless.org/patches/delkey + + +## Why do images not work in st using the w3m image hack? + +w3mimg uses a hack that draws an image on top of the terminal emulator Drawable +window. The hack relies on the terminal to use a single buffer to draw its +contents directly. + +st uses double-buffered drawing so the image is quickly replaced and may show a +short flicker effect. + +Below is a patch example to change st double-buffering to a single Drawable +buffer. + +diff --git a/x.c b/x.c +--- a/x.c ++++ b/x.c +@@ -732,10 +732,6 @@ xresize(int col, int row) + win.tw = col * win.cw; + win.th = row * win.ch; + +- XFreePixmap(xw.dpy, xw.buf); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); +- XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +@@ -1148,8 +1144,7 @@ xinit(int cols, int rows) + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = xw.win; + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) + void + xfinishdraw(void) + { +- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, +- win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); + + +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. + +As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5: +https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS \ No newline at end of file diff --git a/LEGACY b/LEGACY new file mode 100644 index 0000000..bf28b1e --- /dev/null +++ b/LEGACY @@ -0,0 +1,17 @@ +A STATEMENT ON LEGACY SUPPORT + +In the terminal world there is much cruft that comes from old and unsup‐ +ported terminals that inherit incompatible modes and escape sequences +which noone is able to know, except when he/she comes from that time and +developed a graphical vt100 emulator at that time. + +One goal of st is to only support what is really needed. When you en‐ +counter a sequence which you really need, implement it. But while you +are at it, do not add the other cruft you might encounter while sneek‐ +ing at other terminal emulators. History has bloated them and there is +no real evidence that most of the sequences are used today. + + +Christoph Lohmann <20h@r-36.net> +2012-09-13T07:00:36.081271045+02:00 + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3cbf420 --- /dev/null +++ b/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2022 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3dc6258 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c $(LIGATURES_C) $(SIXEL_C) +OBJ = $(SRC:.c=.o) + +all: st + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h $(LIGATURES_H) + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(LIGATURES_H) $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + mkdir -p $(DESTDIR)$(PREFIX)/share/applications # desktop-entry patch + test -f ${DESTDIR}${PREFIX}/share/applications/st.desktop || cp -n st.desktop $(DESTDIR)$(PREFIX)/share/applications # desktop-entry patch + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + rm -f $(DESTDIR)$(PREFIX)/share/applications/st.desktop # desktop-entry patch + +.PHONY: all clean dist install uninstall diff --git a/README b/README new file mode 100644 index 0000000..6a846ed --- /dev/null +++ b/README @@ -0,0 +1,34 @@ +st - simple terminal +-------------------- +st is a simple terminal emulator for X which sucks less. + + +Requirements +------------ +In order to build st you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (st is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install st (if +necessary as root): + + make clean install + + +Running st +---------- +If you did not install st with make clean install, you must compile +the st terminfo entry with the following command: + + tic -sx st.info + +See the man page for additional details. + +Credits +------- +Based on Aurélien APTEL bt source code. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..5f74cd5 --- /dev/null +++ b/TODO @@ -0,0 +1,28 @@ +vt emulation +------------ + +* double-height support + +code & interface +---------------- + +* add a simple way to do multiplexing + +drawing +------- +* add diacritics support to xdraws() + * switch to a suckless font drawing library +* make the font cache simpler +* add better support for brightening of the upper colors + +bugs +---- + +* fix shift up/down (shift selection in emacs) +* remove DEC test sequence when appropriate + +misc +---- + + $ grep -nE 'XXX|TODO' st.c + diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..a22e019 --- /dev/null +++ b/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..631933d --- /dev/null +++ b/config.def.h @@ -0,0 +1,375 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +/* Spare fonts */ +static char *font2[] = { +/* "Inconsolata for Powerline:pixelsize=12:antialias=true:autohint=true", */ +/* "Hack Nerd Font Mono:pixelsize=11:antialias=true:autohint=true", */ +}; + + +static int borderpx = 2; + +/* modkey options: ControlMask, ShiftMask or XK_ANY_MOD */ +static uint url_opener_modkey = XK_ANY_MOD; +static char *url_opener = "xdg-open"; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?12;4c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * Synchronized-Update timeout in ms + * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec + */ +static uint su_timeout = 200; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * 1: render most of the lines/blocks characters without using the font for + * perfect alignment between cells (U2500 - U259F except dashes/diagonals). + * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. + * 0: disable (render all U25XX glyphs normally from the font). + */ +const int boxdraw = 0; +const int boxdraw_bold = 0; + +/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ +const int boxdraw_braille = 0; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; +float alphaUnfocused = 0.6; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#add8e6", /* 256 -> cursor */ + "#555555", /* 257 -> rev cursor*/ + "#000000", /* 258 -> bg */ + "#e5e5e5", /* 259 -> fg */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultbg = 0; +unsigned int bg = 17, bgUnfocused = 16; +unsigned int defaultfg = 259; +unsigned int defaultcs = 256; +unsigned int defaultrcs = 257; + + +/* + * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81 + * Default style of cursor + * 0: Blinking block + * 1: Blinking block (default) + * 2: Steady block ("â–ˆ") + * 3: Blinking underline + * 4: Steady underline ("_") + * 5: Blinking bar + * 6: Steady bar ("|") + * 7: Blinking st cursor + * 8: Steady st cursor + */ +static unsigned int cursorstyle = 1; +static Rune stcursor = 0x2603; /* snowman (U+2603) */ + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default shape of the mouse cursor + */ +static char* mouseshape = "xterm"; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + { "color15", STRING, &colorname[15] }, + { "background", STRING, &colorname[258] }, + { "foreground", STRING, &colorname[259] }, + { "cursorColor", STRING, &colorname[256] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, + { "alpha", FLOAT, &alpha }, + { "alphaUnfocused",FLOAT, &alphaUnfocused }, +}; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release screen */ + { XK_ANY_MOD, Button2, clippaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static char *openurlcmd[] = { "/bin/sh", "-c", + "xurls | dmenu -l 10 -w $WINDOWID | xargs -r open", + "externalpipe", NULL }; + +static char *setbgcolorcmd[] = { "/bin/sh", "-c", + "printf '\033]11;#008000\007'", + "externalpipein", NULL }; + +static Shortcut shortcuts[] = { + /* mask keysym function argument screen */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_O, changealpha, {.f = +0.05} }, + { TERMMOD, XK_P, changealpha, {.f = -0.05} }, + //{ TERMMOD, XK_, changealphaunfocused, {.f = +0.05} }, + //{ TERMMOD, XK_, changealphaunfocused, {.f = -0.05} }, + { TERMMOD, XK_Y, clippaste, {.i = 0} }, + { ShiftMask, XK_Insert, clippaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { TERMMOD, XK_Return, newterm, {.i = 0} }, + { TERMMOD, XK_I, iso14755, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + +/* + * plumb_cmd is run on mouse button 3 click, with argument set to + * current selection and with cwd set to the cwd of the active shell + */ +static char *plumb_cmd = "plumb"; + +/** + * Undercurl style. Set UNDERCURL_STYLE to one of the available styles. + * + * Curly: Dunno how to draw it *shrug* + * _ _ _ _ + * ( ) ( ) ( ) ( ) + * (_) (_) (_) (_) + * + * Spiky: + * /\ /\ /\ /\ + * \/ \/ \/ + * + * Capped: + * _ _ _ + * / \ / \ / \ + * \_/ \_/ + */ +// Available styles +#define UNDERCURL_CURLY 0 +#define UNDERCURL_SPIKY 1 +#define UNDERCURL_CAPPED 2 +// Active style +#define UNDERCURL_STYLE UNDERCURL_SPIKY diff --git a/config.h b/config.h new file mode 100644 index 0000000..4c988d2 --- /dev/null +++ b/config.h @@ -0,0 +1,382 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "FiraCode Nerd Font Mono:pixelsize=14"; +/* Spare fonts */ +static char *font2[] = { + "Noto Color Emoji:pixelsize=14", +}; + + +static int borderpx = 2; + +/* modkey options: ControlMask, ShiftMask or XK_ANY_MOD */ +static uint url_opener_modkey = XK_ANY_MOD; +static char *url_opener = "xdg-open"; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?12;4c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" `'\"()[]{}=,"; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 3; +static double maxlatency = 25; + +/* + * Synchronized-Update timeout in ms + * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec + */ +static uint su_timeout = 200; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * 1: render most of the lines/blocks characters without using the font for + * perfect alignment between cells (U2500 - U259F except dashes/diagonals). + * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. + * 0: disable (render all U25XX glyphs normally from the font). + */ +const int boxdraw = 1; +const int boxdraw_bold = 0; + +/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ +const int boxdraw_braille = 1; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.85; +float alphaUnfocused = 0.75; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "#15161E", + "#f7768e", + "#9ece6a", + "#e0af68", + "#7aa2f7", + "#bb9af7", + "#7dcfff", + "#a9b1d6", + + /* 8 bright colors */ + "#414868", + "#f7768e", + "#9ece6a", + "#e0af68", + "#7aa2f7", + "#bb9af7", + "#7dcfff", + "#c0caf5", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#c0caf5", /* default cursor colour */ + "#1a1b26", /* default inverted cursor colour */ + "#c0caf5", /* default foreground colour */ + "#1a1b26", /* default background colour */ + "#abb7ed", /* unfocused foreground colour */ + "#14141a", /* unfocused background colour */ + // "#c0caf5", /* default selection foreground colour */ + // "#33467C", /* default selection background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultbg = 0; +unsigned int bg = 259, bgUnfocused = 261; +unsigned int defaultfg = 258; +unsigned int defaultcs = 256; +unsigned int defaultrcs = 257; + + +/* + * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81 + * Default style of cursor + * 0: Blinking block + * 1: Blinking block (default) + * 2: Steady block ("â–ˆ") + * 3: Blinking underline + * 4: Steady underline ("_") + * 5: Blinking bar + * 6: Steady bar ("|") + * 7: Blinking st cursor + * 8: Steady st cursor + */ +static unsigned int cursorstyle = 1; +static Rune stcursor = 0x2603; /* snowman (U+2603) */ + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default shape of the mouse cursor + */ +static char* mouseshape = "xterm"; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + { "color15", STRING, &colorname[15] }, + { "cursorColor", STRING, &colorname[256] }, + { "foreground", STRING, &colorname[258] }, + { "background", STRING, &colorname[259] }, + { "unfocusedForeground", STRING, &colorname[260] }, + { "unfocusedBackground", STRING, &colorname[261] }, + // { "selectionForeground", STRING, &colorname[262] }, + // { "selectionBackground", STRING, &colorname[263] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, + { "alpha", FLOAT, &alpha }, + { "alphaUnfocused",FLOAT, &alphaUnfocused }, +}; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release screen */ + // { XK_ANY_MOD, Button2, clippaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static char *openurlcmd[] = { "/bin/sh", "-c", + "xurls | dmenu -l 10 -w $WINDOWID | xargs -r open", + "externalpipe", NULL }; + +static char *setbgcolorcmd[] = { "/bin/sh", "-c", + "printf '\033]11;#008000\007'", + "externalpipein", NULL }; + +static Shortcut shortcuts[] = { + /* mask keysym function argument screen */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +3} }, + { TERMMOD, XK_Next, zoom, {.f = -3} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_O, changealpha, {.f = +0.05} }, + { TERMMOD, XK_P, changealpha, {.f = -0.05} }, + //{ TERMMOD, XK_, changealphaunfocused, {.f = +0.05} }, + //{ TERMMOD, XK_, changealphaunfocused, {.f = -0.05} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { TERMMOD, XK_Return, newterm, {.i = 0} }, + { TERMMOD, XK_I, iso14755, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + +/* + * plumb_cmd is run on mouse button 3 click, with argument set to + * current selection and with cwd set to the cwd of the active shell + */ +static char *plumb_cmd = "plumb"; + +/** + * Undercurl style. Set UNDERCURL_STYLE to one of the available styles. + * + * Curly: Dunno how to draw it *shrug* + * _ _ _ _ + * ( ) ( ) ( ) ( ) + * (_) (_) (_) (_) + * + * Spiky: + * /\ /\ /\ /\ + * \/ \/ \/ + * + * Capped: + * _ _ _ + * / \ / \ / \ + * \_/ \_/ + */ +// Available styles +#define UNDERCURL_CURLY 0 +#define UNDERCURL_SPIKY 1 +#define UNDERCURL_CAPPED 2 +// Active style +#define UNDERCURL_STYLE UNDERCURL_CURLY diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..647bfb5 --- /dev/null +++ b/config.mk @@ -0,0 +1,53 @@ +# st version +VERSION = 0.9 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# Uncomment this for the alpha patch / ALPHA_PATCH +XRENDER = -lXrender + +# Uncomment this for the themed cursor patch / THEMED_CURSOR_PATCH +XCURSOR = -lXcursor + +# Uncomment the lines below for the ligatures patch / LIGATURES_PATCH +LIGATURES_C = hb.c +LIGATURES_H = hb.h +LIGATURES_INC = `$(PKG_CONFIG) --cflags harfbuzz` +LIGATURES_LIBS = `$(PKG_CONFIG) --libs harfbuzz` + +# Uncomment this for the SIXEL patch / SIXEL_PATCH +SIXEL_C = sixel.c sixel_hls.c + +# includes and libs, uncomment harfbuzz for the ligatures patch +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` \ + $(LIGATURES_INC) +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft ${XRENDER} ${XCURSOR}\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` \ + $(LIGATURES_LIBS) + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `pkg-config --libs fontconfig` \ +# `pkg-config --libs freetype2` +#MANPREFIX = ${PREFIX}/man + +# compiler and linker +# CC = c99 diff --git a/hb.c b/hb.c new file mode 100644 index 0000000..db845e7 --- /dev/null +++ b/hb.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "hb.h" + +#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } + +hb_font_t *hbfindfont(XftFont *match); + +typedef struct { + XftFont *match; + hb_font_t *font; +} HbFontMatch; + +static int hbfontslen = 0; +static HbFontMatch *hbfontcache = NULL; + +/* + * Poplulate the array with a list of font features, wrapped in FEATURE macro, + * e. g. + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') + */ +hb_feature_t features[] = { 0 }; + +void +hbunloadfonts() +{ + for (int i = 0; i < hbfontslen; i++) { + hb_font_destroy(hbfontcache[i].font); + XftUnlockFace(hbfontcache[i].match); + } + + if (hbfontcache != NULL) { + free(hbfontcache); + hbfontcache = NULL; + } + hbfontslen = 0; +} + +hb_font_t * +hbfindfont(XftFont *match) +{ + for (int i = 0; i < hbfontslen; i++) { + if (hbfontcache[i].match == match) + return hbfontcache[i].font; + } + + /* Font not found in cache, caching it now. */ + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); + FT_Face face = XftLockFace(match); + hb_font_t *font = hb_ft_font_create(face, NULL); + if (font == NULL) + die("Failed to load Harfbuzz font."); + + hbfontcache[hbfontslen].match = match; + hbfontcache[hbfontslen].font = font; + hbfontslen += 1; + + return font; +} + +void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { + Rune rune; + ushort mode = USHRT_MAX; + unsigned int glyph_count; + int i, end = start + length; + + hb_font_t *font = hbfindfont(xfont); + if (font == NULL) + return; + + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + + /* Fill buffer with codepoints. */ + for (i = start; i < end; i++) { + rune = glyphs[i].u; + mode = glyphs[i].mode; + if (mode & ATTR_WDUMMY) + rune = 0x0020; + hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1); + } + + /* Shape the segment. */ + hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); + + /* Get new glyph info. */ + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); + hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); + + /** Fill the output. */ + data->buffer = buffer; + data->glyphs = info; + data->positions = pos; + data->count = glyph_count; +} + +void hbcleanup(HbTransformData *data) { + hb_buffer_destroy(data->buffer); + memset(data, 0, sizeof(HbTransformData)); +} diff --git a/hb.h b/hb.h new file mode 100644 index 0000000..3b0ef44 --- /dev/null +++ b/hb.h @@ -0,0 +1,14 @@ +#include +#include +#include + +typedef struct { + hb_buffer_t *buffer; + hb_glyph_info_t *glyphs; + hb_glyph_position_t *positions; + unsigned int count; +} HbTransformData; + +void hbunloadfonts(); +void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); +void hbcleanup(HbTransformData *); diff --git a/patch/alpha.c b/patch/alpha.c new file mode 100644 index 0000000..ece4afc --- /dev/null +++ b/patch/alpha.c @@ -0,0 +1,28 @@ +float +clamp(float value, float lower, float upper) { + if (value < lower) + return lower; + if (value > upper) + return upper; + return value; +} + +void +changealpha(const Arg *arg) +{ + if ((alpha > 0 && arg->f < 0) || (alpha < 1 && arg->f > 0)) + alpha += arg->f; + alpha = clamp(alpha, 0.0, 1.0); + xloadcols(); + redraw(); +} + +void +changealphaunfocused(const Arg *arg) +{ + if ((alphaUnfocused > 0 && arg->f < 0) || (alphaUnfocused < 1 && arg->f > 0)) + alphaUnfocused += arg->f; + alphaUnfocused = clamp(alphaUnfocused, 0.0, 1.0); + xloadcols(); + redraw(); +} diff --git a/patch/alpha.h b/patch/alpha.h new file mode 100644 index 0000000..e681b4f --- /dev/null +++ b/patch/alpha.h @@ -0,0 +1,3 @@ +static float clamp(float value, float lower, float upper); +static void changealpha(const Arg *); +static void changealphaunfocused(const Arg *arg); diff --git a/patch/boxdraw.c b/patch/boxdraw.c new file mode 100644 index 0000000..ef1e110 --- /dev/null +++ b/patch/boxdraw.c @@ -0,0 +1,192 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ + +#include + +/* Rounded non-negative integers division of n / d */ +#define DIV(n, d) (((n) + (d) / 2) / (d)) + +static Display *xdpy; +static Colormap xcmap; +static XftDraw *xd; +static Visual *xvis; + +static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort); +static void drawboxlines(int, int, int, int, XftColor *, ushort); + +/* public API */ + +void +boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis) +{ + xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis; +} + +int +isboxdraw(Rune u) +{ + Rune block = u & ~0xff; + return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) || + (boxdraw_braille && block == 0x2800); +} + +/* the "index" is actually the entire shape data encoded as ushort */ +ushort +boxdrawindex(const Glyph *g) +{ + if (boxdraw_braille && (g->u & ~0xff) == 0x2800) + return BRL | (uint8_t)g->u; + if (boxdraw_bold && (g->mode & ATTR_BOLD)) + return BDB | boxdata[(uint8_t)g->u]; + return boxdata[(uint8_t)g->u]; +} + +void +drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg, + const XftGlyphFontSpec *specs, int len) +{ + for ( ; len-- > 0; x += cw, specs++) + drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph); +} + +/* implementation */ + +void +drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd) +{ + ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */ + if (bd & (BDL | BDA)) { + /* lines (light/double/heavy/arcs) */ + drawboxlines(x, y, w, h, fg, bd); + + } else if (cat == BBD) { + /* lower (8-X)/8 block */ + int d = DIV((uint8_t)bd * h, 8); + XftDrawRect(xd, fg, x, y + d, w, h - d); + + } else if (cat == BBU) { + /* upper X/8 block */ + XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8)); + + } else if (cat == BBL) { + /* left X/8 block */ + XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h); + + } else if (cat == BBR) { + /* right (8-X)/8 block */ + int d = DIV((uint8_t)bd * w, 8); + XftDrawRect(xd, fg, x + d, y, w - d, h); + + } else if (cat == BBQ) { + /* Quadrants */ + int w2 = DIV(w, 2), h2 = DIV(h, 2); + if (bd & TL) + XftDrawRect(xd, fg, x, y, w2, h2); + if (bd & TR) + XftDrawRect(xd, fg, x + w2, y, w - w2, h2); + if (bd & BL) + XftDrawRect(xd, fg, x, y + h2, w2, h - h2); + if (bd & BR) + XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2); + + } else if (bd & BBS) { + /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */ + int d = (uint8_t)bd; + XftColor xfc; + XRenderColor xrc = { .alpha = 0xffff }; + + xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4); + xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4); + xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4); + + XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc); + XftDrawRect(xd, &xfc, x, y, w, h); + XftColorFree(xdpy, xvis, xcmap, &xfc); + + } else if (cat == BRL) { + /* braille, each data bit corresponds to one dot at 2x4 grid */ + int w1 = DIV(w, 2); + int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4); + + if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1); + if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1); + if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2); + if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1); + if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1); + if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2); + if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3); + if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3); + + } +} + +void +drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd) +{ + /* s: stem thickness. width/8 roughly matches underscore thickness. */ + /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */ + /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */ + int mwh = MIN(w, h); + int base_s = MAX(1, DIV(mwh, 8)); + int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */ + int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s; + int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2); + /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */ + /* The base length (per direction till edge) includes this square. */ + + int light = bd & (LL | LU | LR | LD); + int double_ = bd & (DL | DU | DR | DD); + + if (light) { + /* d: additional (negative) length to not-draw the center */ + /* texel - at arcs and avoid drawing inside (some) doubles */ + int arc = bd & BDA; + int multi_light = light & (light - 1); + int multi_double = double_ & (double_ - 1); + /* light crosses double only at DH+LV, DV+LH (ref. shapes) */ + int d = arc || (multi_double && !multi_light) ? -s : 0; + + if (bd & LL) + XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s); + if (bd & LU) + XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d); + if (bd & LR) + XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s); + if (bd & LD) + XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d); + } + + /* double lines - also align with light to form heavy when combined */ + if (double_) { + /* + * going clockwise, for each double-ray: p is additional length + * to the single-ray nearer to the previous direction, and n to + * the next. p and n adjust from the base length to lengths + * which consider other doubles - shorter to avoid intersections + * (p, n), or longer to draw the far-corner texel (n). + */ + int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD; + if (dl) { + int p = dd ? -s : 0, n = du ? -s : dd ? s : 0; + XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s); + XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s); + } + if (du) { + int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0; + XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p); + XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n); + } + if (dr) { + int p = du ? -s : 0, n = dd ? -s : du ? s : 0; + XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s); + XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s); + } + if (dd) { + int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0; + XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p); + XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n); + } + } +} diff --git a/patch/boxdraw.h b/patch/boxdraw.h new file mode 100644 index 0000000..7890500 --- /dev/null +++ b/patch/boxdraw.h @@ -0,0 +1,214 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ + +/* + * U+25XX codepoints data + * + * References: + * http://www.unicode.org/charts/PDF/U2500.pdf + * http://www.unicode.org/charts/PDF/U2580.pdf + * + * Test page: + * https://github.com/GNOME/vte/blob/master/doc/boxes.txt + */ + +/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */ +/* Categories (mutually exclusive except BDB): */ +/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */ +#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */ +#define BDA (1<<9) /* Box Draw Arc (light) */ + +#define BBD (1<<10) /* Box Block Down (lower) X/8 */ +#define BBL (2<<10) /* Box Block Left X/8 */ +#define BBU (3<<10) /* Box Block Upper X/8 */ +#define BBR (4<<10) /* Box Block Right X/8 */ +#define BBQ (5<<10) /* Box Block Quadrants */ +#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */ + +#define BBS (1<<14) /* Box Block Shades */ +#define BDB (1<<15) /* Box Draw is Bold */ + +/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */ +/* Heavy is light+double (literally drawing light+double align to form heavy) */ +#define LL (1<<0) +#define LU (1<<1) +#define LR (1<<2) +#define LD (1<<3) +#define LH (LL+LR) +#define LV (LU+LD) + +#define DL (1<<4) +#define DU (1<<5) +#define DR (1<<6) +#define DD (1<<7) +#define DH (DL+DR) +#define DV (DU+DD) + +#define HL (LL+DL) +#define HU (LU+DU) +#define HR (LR+DR) +#define HD (LD+DD) +#define HH (HL+HR) +#define HV (HU+HD) + +/* (BBQ) Quadrants Top/Bottom x Left/Right */ +#define TL (1<<0) +#define TR (1<<1) +#define BL (1<<2) +#define BR (1<<3) + +/* Data for U+2500 - U+259F except dashes/diagonals */ +static const unsigned short boxdata[256] = { + /* light lines */ + [0x00] = BDL + LH, /* light horizontal */ + [0x02] = BDL + LV, /* light vertical */ + [0x0c] = BDL + LD + LR, /* light down and right */ + [0x10] = BDL + LD + LL, /* light down and left */ + [0x14] = BDL + LU + LR, /* light up and right */ + [0x18] = BDL + LU + LL, /* light up and left */ + [0x1c] = BDL + LV + LR, /* light vertical and right */ + [0x24] = BDL + LV + LL, /* light vertical and left */ + [0x2c] = BDL + LH + LD, /* light horizontal and down */ + [0x34] = BDL + LH + LU, /* light horizontal and up */ + [0x3c] = BDL + LV + LH, /* light vertical and horizontal */ + [0x74] = BDL + LL, /* light left */ + [0x75] = BDL + LU, /* light up */ + [0x76] = BDL + LR, /* light right */ + [0x77] = BDL + LD, /* light down */ + + /* heavy [+light] lines */ + [0x01] = BDL + HH, + [0x03] = BDL + HV, + [0x0d] = BDL + HR + LD, + [0x0e] = BDL + HD + LR, + [0x0f] = BDL + HD + HR, + [0x11] = BDL + HL + LD, + [0x12] = BDL + HD + LL, + [0x13] = BDL + HD + HL, + [0x15] = BDL + HR + LU, + [0x16] = BDL + HU + LR, + [0x17] = BDL + HU + HR, + [0x19] = BDL + HL + LU, + [0x1a] = BDL + HU + LL, + [0x1b] = BDL + HU + HL, + [0x1d] = BDL + HR + LV, + [0x1e] = BDL + HU + LD + LR, + [0x1f] = BDL + HD + LR + LU, + [0x20] = BDL + HV + LR, + [0x21] = BDL + HU + HR + LD, + [0x22] = BDL + HD + HR + LU, + [0x23] = BDL + HV + HR, + [0x25] = BDL + HL + LV, + [0x26] = BDL + HU + LD + LL, + [0x27] = BDL + HD + LU + LL, + [0x28] = BDL + HV + LL, + [0x29] = BDL + HU + HL + LD, + [0x2a] = BDL + HD + HL + LU, + [0x2b] = BDL + HV + HL, + [0x2d] = BDL + HL + LD + LR, + [0x2e] = BDL + HR + LL + LD, + [0x2f] = BDL + HH + LD, + [0x30] = BDL + HD + LH, + [0x31] = BDL + HD + HL + LR, + [0x32] = BDL + HR + HD + LL, + [0x33] = BDL + HH + HD, + [0x35] = BDL + HL + LU + LR, + [0x36] = BDL + HR + LU + LL, + [0x37] = BDL + HH + LU, + [0x38] = BDL + HU + LH, + [0x39] = BDL + HU + HL + LR, + [0x3a] = BDL + HU + HR + LL, + [0x3b] = BDL + HH + HU, + [0x3d] = BDL + HL + LV + LR, + [0x3e] = BDL + HR + LV + LL, + [0x3f] = BDL + HH + LV, + [0x40] = BDL + HU + LH + LD, + [0x41] = BDL + HD + LH + LU, + [0x42] = BDL + HV + LH, + [0x43] = BDL + HU + HL + LD + LR, + [0x44] = BDL + HU + HR + LD + LL, + [0x45] = BDL + HD + HL + LU + LR, + [0x46] = BDL + HD + HR + LU + LL, + [0x47] = BDL + HH + HU + LD, + [0x48] = BDL + HH + HD + LU, + [0x49] = BDL + HV + HL + LR, + [0x4a] = BDL + HV + HR + LL, + [0x4b] = BDL + HV + HH, + [0x78] = BDL + HL, + [0x79] = BDL + HU, + [0x7a] = BDL + HR, + [0x7b] = BDL + HD, + [0x7c] = BDL + HR + LL, + [0x7d] = BDL + HD + LU, + [0x7e] = BDL + HL + LR, + [0x7f] = BDL + HU + LD, + + /* double [+light] lines */ + [0x50] = BDL + DH, + [0x51] = BDL + DV, + [0x52] = BDL + DR + LD, + [0x53] = BDL + DD + LR, + [0x54] = BDL + DR + DD, + [0x55] = BDL + DL + LD, + [0x56] = BDL + DD + LL, + [0x57] = BDL + DL + DD, + [0x58] = BDL + DR + LU, + [0x59] = BDL + DU + LR, + [0x5a] = BDL + DU + DR, + [0x5b] = BDL + DL + LU, + [0x5c] = BDL + DU + LL, + [0x5d] = BDL + DL + DU, + [0x5e] = BDL + DR + LV, + [0x5f] = BDL + DV + LR, + [0x60] = BDL + DV + DR, + [0x61] = BDL + DL + LV, + [0x62] = BDL + DV + LL, + [0x63] = BDL + DV + DL, + [0x64] = BDL + DH + LD, + [0x65] = BDL + DD + LH, + [0x66] = BDL + DD + DH, + [0x67] = BDL + DH + LU, + [0x68] = BDL + DU + LH, + [0x69] = BDL + DH + DU, + [0x6a] = BDL + DH + LV, + [0x6b] = BDL + DV + LH, + [0x6c] = BDL + DH + DV, + + /* (light) arcs */ + [0x6d] = BDA + LD + LR, + [0x6e] = BDA + LD + LL, + [0x6f] = BDA + LU + LL, + [0x70] = BDA + LU + LR, + + /* Lower (Down) X/8 block (data is 8 - X) */ + [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4, + [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0, + + /* Left X/8 block (data is X) */ + [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4, + [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1, + + /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */ + [0x80] = BBU + 4, [0x94] = BBU + 1, + [0x90] = BBR + 4, [0x95] = BBR + 7, + + /* Quadrants */ + [0x96] = BBQ + BL, + [0x97] = BBQ + BR, + [0x98] = BBQ + TL, + [0x99] = BBQ + TL + BL + BR, + [0x9a] = BBQ + TL + BR, + [0x9b] = BBQ + TL + TR + BL, + [0x9c] = BBQ + TL + TR + BR, + [0x9d] = BBQ + TR, + [0x9e] = BBQ + BL + TR, + [0x9f] = BBQ + BL + TR + BR, + + /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */ + [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3, + + /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */ + /* U+2571 - U+2573: unsupported (diagonals) */ +}; diff --git a/patch/fixkeyboardinput.c b/patch/fixkeyboardinput.c new file mode 100644 index 0000000..073cddb --- /dev/null +++ b/patch/fixkeyboardinput.c @@ -0,0 +1,811 @@ +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { + XK_space, + XK_m, + XK_i, + XK_A, + XK_B, + XK_C, + XK_D, + XK_E, + XK_F, + XK_G, + XK_H, + XK_I, + XK_K, + XK_J, + XK_L, + XK_M, + XK_N, + XK_O, + XK_P, + XK_Q, + XK_R, + XK_S, + XK_T, + XK_U, + XK_V, + XK_W, + XK_X, + XK_Y, + XK_Z, + XK_0, + XK_1, + XK_2, + XK_3, + XK_4, + XK_5, + XK_6, + XK_7, + XK_8, + XK_9, + XK_exclam, + XK_quotedbl, + XK_numbersign, + XK_dollar, + XK_percent, + XK_ampersand, + XK_apostrophe, + XK_parenleft, + XK_parenright, + XK_asterisk, + XK_plus, + XK_comma, + XK_minus, + XK_period, + XK_slash, + XK_colon, + XK_semicolon, + XK_less, + XK_equal, + XK_greater, + XK_question, + XK_at, + XK_bracketleft, + XK_backslash, + XK_bracketright, + XK_asciicircum, + XK_underscore, + XK_grave, + XK_braceleft, + XK_bar, + XK_braceright, + XK_asciitilde, +}; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_NO_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, + + // libtermkey compatible keyboard input + { XK_KP_Home, XK_NO_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_NO_MOD, "\033[1~", 0, +1}, + { XK_KP_Home, ControlMask, "\033[149;5u", 0, 0}, + { XK_KP_Home, ControlMask|ShiftMask, "\033[149;6u", 0, 0}, + { XK_KP_Home, Mod1Mask, "\033[149;3u", 0, 0}, + { XK_KP_Home, Mod1Mask|ControlMask, "\033[149;7u", 0, 0}, + { XK_KP_Home, Mod1Mask|ControlMask|ShiftMask, "\033[149;8u", 0, 0}, + { XK_KP_Home, Mod1Mask|ShiftMask, "\033[149;4u", 0, 0}, + { XK_KP_Home, ShiftMask, "\033[149;2u", 0, 0}, + { XK_KP_Up, XK_NO_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_NO_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_NO_MOD, "\033OA", 0, +1}, + { XK_KP_Up, ControlMask, "\033[151;5u", 0, 0}, + { XK_KP_Up, ControlMask|ShiftMask, "\033[151;6u", 0, 0}, + { XK_KP_Up, Mod1Mask, "\033[151;3u", 0, 0}, + { XK_KP_Up, Mod1Mask|ControlMask, "\033[151;7u", 0, 0}, + { XK_KP_Up, Mod1Mask|ControlMask|ShiftMask, "\033[151;8u", 0, 0}, + { XK_KP_Up, Mod1Mask|ShiftMask, "\033[151;4u", 0, 0}, + { XK_KP_Up, ShiftMask, "\033[151;2u", 0, 0}, + { XK_KP_Down, XK_NO_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_NO_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_NO_MOD, "\033OB", 0, +1}, + { XK_KP_Down, ControlMask, "\033[153;5u", 0, 0}, + { XK_KP_Down, ControlMask|ShiftMask, "\033[153;6u", 0, 0}, + { XK_KP_Down, Mod1Mask, "\033[153;3u", 0, 0}, + { XK_KP_Down, Mod1Mask|ControlMask, "\033[153;7u", 0, 0}, + { XK_KP_Down, Mod1Mask|ControlMask|ShiftMask, "\033[153;8u", 0, 0}, + { XK_KP_Down, Mod1Mask|ShiftMask, "\033[153;4u", 0, 0}, + { XK_KP_Down, ShiftMask, "\033[153;2u", 0, 0}, + { XK_KP_Left, XK_NO_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_NO_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_NO_MOD, "\033OD", 0, +1}, + { XK_KP_Left, ControlMask, "\033[150;5u", 0, 0}, + { XK_KP_Left, ControlMask|ShiftMask, "\033[150;6u", 0, 0}, + { XK_KP_Left, Mod1Mask, "\033[150;3u", 0, 0}, + { XK_KP_Left, Mod1Mask|ControlMask, "\033[150;7u", 0, 0}, + { XK_KP_Left, Mod1Mask|ControlMask|ShiftMask, "\033[150;8u", 0, 0}, + { XK_KP_Left, Mod1Mask|ShiftMask, "\033[150;4u", 0, 0}, + { XK_KP_Left, ShiftMask, "\033[150;2u", 0, 0}, + { XK_KP_Right, XK_NO_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_NO_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_NO_MOD, "\033OC", 0, +1}, + { XK_KP_Right, ControlMask, "\033[152;5u", 0, 0}, + { XK_KP_Right, ControlMask|ShiftMask, "\033[152;6u", 0, 0}, + { XK_KP_Right, Mod1Mask, "\033[152;3u", 0, 0}, + { XK_KP_Right, Mod1Mask|ControlMask, "\033[152;7u", 0, 0}, + { XK_KP_Right, Mod1Mask|ControlMask|ShiftMask, "\033[152;8u", 0, 0}, + { XK_KP_Right, Mod1Mask|ShiftMask, "\033[152;4u", 0, 0}, + { XK_KP_Right, ShiftMask, "\033[152;2u", 0, 0}, + { XK_KP_Prior, XK_NO_MOD, "\033[5~", 0, 0}, + { XK_KP_Prior, ControlMask, "\033[154;5u", 0, 0}, + { XK_KP_Prior, ControlMask|ShiftMask, "\033[154;6u", 0, 0}, + { XK_KP_Prior, Mod1Mask, "\033[154;3u", 0, 0}, + { XK_KP_Prior, Mod1Mask|ControlMask, "\033[154;7u", 0, 0}, + { XK_KP_Prior, Mod1Mask|ControlMask|ShiftMask, "\033[154;8u", 0, 0}, + { XK_KP_Prior, Mod1Mask|ShiftMask, "\033[154;4u", 0, 0}, + { XK_KP_Begin, XK_NO_MOD, "\033[E", 0, 0}, + { XK_KP_Begin, ControlMask, "\033[157;5u", 0, 0}, + { XK_KP_Begin, ControlMask|ShiftMask, "\033[157;6u", 0, 0}, + { XK_KP_Begin, Mod1Mask, "\033[157;3u", 0, 0}, + { XK_KP_Begin, Mod1Mask|ControlMask, "\033[157;7u", 0, 0}, + { XK_KP_Begin, Mod1Mask|ControlMask|ShiftMask, "\033[157;8u", 0, 0}, + { XK_KP_Begin, Mod1Mask|ShiftMask, "\033[157;4u", 0, 0}, + { XK_KP_Begin, ShiftMask, "\033[157;2u", 0, 0}, + { XK_KP_End, XK_NO_MOD, "\033[4~", 0, 0}, + { XK_KP_End, ControlMask|ShiftMask, "\033[156;6u", 0, 0}, + { XK_KP_End, Mod1Mask, "\033[156;3u", 0, 0}, + { XK_KP_End, Mod1Mask|ControlMask, "\033[156;7u", 0, 0}, + { XK_KP_End, Mod1Mask|ControlMask|ShiftMask, "\033[156;8u", 0, 0}, + { XK_KP_End, Mod1Mask|ShiftMask, "\033[156;4u", 0, 0}, + { XK_KP_Next, XK_NO_MOD, "\033[6~", 0, 0}, + { XK_KP_Next, ControlMask, "\033[155;5u", 0, 0}, + { XK_KP_Next, ControlMask|ShiftMask, "\033[155;6u", 0, 0}, + { XK_KP_Next, Mod1Mask, "\033[155;3u", 0, 0}, + { XK_KP_Next, Mod1Mask|ControlMask, "\033[155;7u", 0, 0}, + { XK_KP_Next, Mod1Mask|ControlMask|ShiftMask, "\033[155;8u", 0, 0}, + { XK_KP_Next, Mod1Mask|ShiftMask, "\033[155;4u", 0, 0}, + { XK_KP_Insert, XK_NO_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_NO_MOD, "\033[2~", +1, 0}, + { XK_KP_Insert, ControlMask|ShiftMask, "\033[158;6u", 0, 0}, + { XK_KP_Insert, Mod1Mask, "\033[158;3u", 0, 0}, + { XK_KP_Insert, Mod1Mask|ControlMask, "\033[158;7u", 0, 0}, + { XK_KP_Insert, Mod1Mask|ControlMask|ShiftMask, "\033[158;8u", 0, 0}, + { XK_KP_Insert, Mod1Mask|ShiftMask, "\033[158;4u", 0, 0}, + { XK_KP_Delete, XK_NO_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_NO_MOD, "\033[3~", +1, 0}, + { XK_KP_Delete, ControlMask|ShiftMask, "\033[159;6u", 0, 0}, + { XK_KP_Delete, Mod1Mask, "\033[159;3u", 0, 0}, + { XK_KP_Delete, Mod1Mask|ControlMask, "\033[159;7u", 0, 0}, + { XK_KP_Delete, Mod1Mask|ControlMask|ShiftMask, "\033[159;8u", 0, 0}, + { XK_KP_Delete, Mod1Mask|ShiftMask, "\033[159;4u", 0, 0}, + { XK_KP_Multiply, XK_NO_MOD, "\033Oj", +2, 0}, + { XK_KP_Multiply, ControlMask, "\033[170;5u", 0, 0}, + { XK_KP_Multiply, ControlMask|ShiftMask, "\033[170;6u", 0, 0}, + { XK_KP_Multiply, Mod1Mask, "\033[170;3u", 0, 0}, + { XK_KP_Multiply, Mod1Mask|ControlMask, "\033[170;7u", 0, 0}, + { XK_KP_Multiply, Mod1Mask|ControlMask|ShiftMask, "\033[170;8u", 0, 0}, + { XK_KP_Multiply, Mod1Mask|ShiftMask, "\033[170;4u", 0, 0}, + { XK_KP_Multiply, ShiftMask, "\033[170;2u", 0, 0}, + { XK_KP_Add, XK_NO_MOD, "\033Ok", +2, 0}, + { XK_KP_Add, ControlMask, "\033[171;5u", 0, 0}, + { XK_KP_Add, ControlMask|ShiftMask, "\033[171;6u", 0, 0}, + { XK_KP_Add, Mod1Mask, "\033[171;3u", 0, 0}, + { XK_KP_Add, Mod1Mask|ControlMask, "\033[171;7u", 0, 0}, + { XK_KP_Add, Mod1Mask|ControlMask|ShiftMask, "\033[171;8u", 0, 0}, + { XK_KP_Add, Mod1Mask|ShiftMask, "\033[171;4u", 0, 0}, + { XK_KP_Add, ShiftMask, "\033[171;2u", 0, 0}, + { XK_KP_Enter, XK_NO_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_NO_MOD, "\r", -1, 0}, + { XK_KP_Enter, XK_NO_MOD, "\r\n", -1, 0}, + { XK_KP_Enter, ControlMask, "\033[141;5u", 0, 0}, + { XK_KP_Enter, ControlMask|ShiftMask, "\033[141;6u", 0, 0}, + { XK_KP_Enter, Mod1Mask, "\033[141;3u", 0, 0}, + { XK_KP_Enter, Mod1Mask|ControlMask, "\033[141;7u", 0, 0}, + { XK_KP_Enter, Mod1Mask|ControlMask|ShiftMask, "\033[141;8u", 0, 0}, + { XK_KP_Enter, Mod1Mask|ShiftMask, "\033[141;4u", 0, 0}, + { XK_KP_Enter, ShiftMask, "\033[141;2u", 0, 0}, + { XK_KP_Subtract, XK_NO_MOD, "\033Om", +2, 0}, + { XK_KP_Subtract, ControlMask, "\033[173;5u", 0, 0}, + { XK_KP_Subtract, ControlMask|ShiftMask, "\033[173;6u", 0, 0}, + { XK_KP_Subtract, Mod1Mask, "\033[173;3u", 0, 0}, + { XK_KP_Subtract, Mod1Mask|ControlMask, "\033[173;7u", 0, 0}, + { XK_KP_Subtract, Mod1Mask|ControlMask|ShiftMask, "\033[173;8u", 0, 0}, + { XK_KP_Subtract, Mod1Mask|ShiftMask, "\033[173;4u", 0, 0}, + { XK_KP_Subtract, ShiftMask, "\033[173;2u", 0, 0}, + { XK_KP_Decimal, XK_NO_MOD, "\033On", +2, 0}, + { XK_KP_Decimal, ControlMask, "\033[174;5u", 0, 0}, + { XK_KP_Decimal, ControlMask|ShiftMask, "\033[174;6u", 0, 0}, + { XK_KP_Decimal, Mod1Mask, "\033[174;3u", 0, 0}, + { XK_KP_Decimal, Mod1Mask|ControlMask, "\033[174;7u", 0, 0}, + { XK_KP_Decimal, Mod1Mask|ControlMask|ShiftMask, "\033[174;8u", 0, 0}, + { XK_KP_Decimal, Mod1Mask|ShiftMask, "\033[174;4u", 0, 0}, + { XK_KP_Decimal, ShiftMask, "\033[174;2u", 0, 0}, + { XK_KP_Divide, XK_NO_MOD, "\033Oo", +2, 0}, + { XK_KP_Divide, ControlMask, "\033[175;5u", 0, 0}, + { XK_KP_Divide, ControlMask|ShiftMask, "\033[175;6u", 0, 0}, + { XK_KP_Divide, Mod1Mask, "\033[175;3u", 0, 0}, + { XK_KP_Divide, Mod1Mask|ControlMask, "\033[175;7u", 0, 0}, + { XK_KP_Divide, Mod1Mask|ControlMask|ShiftMask, "\033[175;8u", 0, 0}, + { XK_KP_Divide, Mod1Mask|ShiftMask, "\033[175;4u", 0, 0}, + { XK_KP_Divide, ShiftMask, "\033[175;2u", 0, 0}, + { XK_KP_0, XK_NO_MOD, "\033Op", +2, 0}, + { XK_KP_0, ControlMask, "\033[176;5u", 0, 0}, + { XK_KP_0, ControlMask|ShiftMask, "\033[176;6u", 0, 0}, + { XK_KP_0, Mod1Mask, "\033[176;3u", 0, 0}, + { XK_KP_0, Mod1Mask|ControlMask, "\033[176;7u", 0, 0}, + { XK_KP_0, Mod1Mask|ControlMask|ShiftMask, "\033[176;8u", 0, 0}, + { XK_KP_0, Mod1Mask|ShiftMask, "\033[176;4u", 0, 0}, + { XK_KP_0, ShiftMask, "\033[176;2u", 0, 0}, + { XK_KP_1, XK_NO_MOD, "\033Oq", +2, 0}, + { XK_KP_0, ControlMask, "\033[177;5u", 0, 0}, + { XK_KP_0, ControlMask|ShiftMask, "\033[177;6u", 0, 0}, + { XK_KP_0, Mod1Mask, "\033[177;3u", 0, 0}, + { XK_KP_0, Mod1Mask|ControlMask, "\033[177;7u", 0, 0}, + { XK_KP_0, Mod1Mask|ControlMask|ShiftMask, "\033[177;8u", 0, 0}, + { XK_KP_0, Mod1Mask|ShiftMask, "\033[177;4u", 0, 0}, + { XK_KP_0, ShiftMask, "\033[177;2u", 0, 0}, + { XK_KP_2, XK_NO_MOD, "\033Or", +2, 0}, + { XK_KP_2, ControlMask, "\033[178;5u", 0, 0}, + { XK_KP_2, ControlMask|ShiftMask, "\033[178;6u", 0, 0}, + { XK_KP_2, Mod1Mask, "\033[178;3u", 0, 0}, + { XK_KP_2, Mod1Mask|ControlMask, "\033[178;7u", 0, 0}, + { XK_KP_2, Mod1Mask|ControlMask|ShiftMask, "\033[178;8u", 0, 0}, + { XK_KP_2, Mod1Mask|ShiftMask, "\033[178;4u", 0, 0}, + { XK_KP_2, ShiftMask, "\033[178;2u", 0, 0}, + { XK_KP_3, XK_NO_MOD, "\033Os", +2, 0}, + { XK_KP_3, ControlMask, "\033[179;5u", 0, 0}, + { XK_KP_3, ControlMask|ShiftMask, "\033[179;6u", 0, 0}, + { XK_KP_3, Mod1Mask, "\033[179;3u", 0, 0}, + { XK_KP_3, Mod1Mask|ControlMask, "\033[179;7u", 0, 0}, + { XK_KP_3, Mod1Mask|ControlMask|ShiftMask, "\033[179;8u", 0, 0}, + { XK_KP_3, Mod1Mask|ShiftMask, "\033[179;4u", 0, 0}, + { XK_KP_3, ShiftMask, "\033[179;2u", 0, 0}, + { XK_KP_4, XK_NO_MOD, "\033Ot", +2, 0}, + { XK_KP_4, ControlMask, "\033[180;5u", 0, 0}, + { XK_KP_4, ControlMask|ShiftMask, "\033[180;6u", 0, 0}, + { XK_KP_4, Mod1Mask, "\033[180;3u", 0, 0}, + { XK_KP_4, Mod1Mask|ControlMask, "\033[180;7u", 0, 0}, + { XK_KP_4, Mod1Mask|ControlMask|ShiftMask, "\033[180;8u", 0, 0}, + { XK_KP_4, Mod1Mask|ShiftMask, "\033[180;4u", 0, 0}, + { XK_KP_4, ShiftMask, "\033[180;2u", 0, 0}, + { XK_KP_5, XK_NO_MOD, "\033Ou", +2, 0}, + { XK_KP_5, ControlMask, "\033[181;5u", 0, 0}, + { XK_KP_5, ControlMask|ShiftMask, "\033[181;6u", 0, 0}, + { XK_KP_5, Mod1Mask, "\033[181;3u", 0, 0}, + { XK_KP_5, Mod1Mask|ControlMask, "\033[181;7u", 0, 0}, + { XK_KP_5, Mod1Mask|ControlMask|ShiftMask, "\033[181;8u", 0, 0}, + { XK_KP_5, Mod1Mask|ShiftMask, "\033[181;4u", 0, 0}, + { XK_KP_5, ShiftMask, "\033[181;2u", 0, 0}, + { XK_KP_6, XK_NO_MOD, "\033Ov", +2, 0}, + { XK_KP_6, ControlMask, "\033[182;5u", 0, 0}, + { XK_KP_6, ControlMask|ShiftMask, "\033[182;6u", 0, 0}, + { XK_KP_6, Mod1Mask, "\033[182;3u", 0, 0}, + { XK_KP_6, Mod1Mask|ControlMask, "\033[182;7u", 0, 0}, + { XK_KP_6, Mod1Mask|ControlMask|ShiftMask, "\033[182;8u", 0, 0}, + { XK_KP_6, Mod1Mask|ShiftMask, "\033[182;4u", 0, 0}, + { XK_KP_6, ShiftMask, "\033[182;2u", 0, 0}, + { XK_KP_7, XK_NO_MOD, "\033Ow", +2, 0}, + { XK_KP_7, ControlMask, "\033[183;5u", 0, 0}, + { XK_KP_7, ControlMask|ShiftMask, "\033[183;6u", 0, 0}, + { XK_KP_7, Mod1Mask, "\033[183;3u", 0, 0}, + { XK_KP_7, Mod1Mask|ControlMask, "\033[183;7u", 0, 0}, + { XK_KP_7, Mod1Mask|ControlMask|ShiftMask, "\033[183;8u", 0, 0}, + { XK_KP_7, Mod1Mask|ShiftMask, "\033[183;4u", 0, 0}, + { XK_KP_7, ShiftMask, "\033[183;2u", 0, 0}, + { XK_KP_8, XK_NO_MOD, "\033Ox", +2, 0}, + { XK_KP_8, ControlMask, "\033[184;5u", 0, 0}, + { XK_KP_8, ControlMask|ShiftMask, "\033[184;6u", 0, 0}, + { XK_KP_8, Mod1Mask, "\033[184;3u", 0, 0}, + { XK_KP_8, Mod1Mask|ControlMask, "\033[184;7u", 0, 0}, + { XK_KP_8, Mod1Mask|ControlMask|ShiftMask, "\033[184;8u", 0, 0}, + { XK_KP_8, Mod1Mask|ShiftMask, "\033[184;4u", 0, 0}, + { XK_KP_8, ShiftMask, "\033[184;2u", 0, 0}, + { XK_KP_9, XK_NO_MOD, "\033Oy", +2, 0}, + { XK_KP_9, ControlMask, "\033[185;5u", 0, 0}, + { XK_KP_9, ControlMask|ShiftMask, "\033[185;6u", 0, 0}, + { XK_KP_9, Mod1Mask, "\033[185;3u", 0, 0}, + { XK_KP_9, Mod1Mask|ControlMask, "\033[185;7u", 0, 0}, + { XK_KP_9, Mod1Mask|ControlMask|ShiftMask, "\033[185;8u", 0, 0}, + { XK_KP_9, Mod1Mask|ShiftMask, "\033[185;4u", 0, 0}, + { XK_KP_9, ShiftMask, "\033[185;2u", 0, 0}, + { XK_BackSpace, ControlMask, "\033[127;5u", 0, 0}, + { XK_BackSpace, ControlMask|ShiftMask, "\033[127;6u", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033[127;3u", 0, 0}, + { XK_BackSpace, Mod1Mask|ControlMask, "\033[127;7u", 0, 0}, + { XK_BackSpace, Mod1Mask|ControlMask|ShiftMask, "\033[127;8u", 0, 0}, + { XK_BackSpace, Mod1Mask|ShiftMask, "\033[127;4u", 0, 0}, + { XK_BackSpace, ShiftMask, "\033[127;2u", 0, 0}, + { XK_Tab, ControlMask, "\033[9;5u", 0, 0}, + { XK_Tab, ControlMask|ShiftMask, "\033[1;5Z", 0, 0}, + { XK_Tab, Mod1Mask, "\033[1;3Z", 0, 0}, + { XK_Tab, Mod1Mask|ControlMask, "\033[1;7Z", 0, 0}, + { XK_Tab, Mod1Mask|ControlMask|ShiftMask, "\033[1;8Z", 0, 0}, + { XK_Tab, Mod1Mask|ShiftMask, "\033[1;4Z", 0, 0}, + { XK_Return, ControlMask, "\033[13;5u", 0, 0}, + { XK_Return, ControlMask|ShiftMask, "\033[13;6u", 0, 0}, + { XK_Return, Mod1Mask, "\033[13;3u", 0, 0}, + { XK_Return, Mod1Mask|ControlMask, "\033[13;7u", 0, 0}, + { XK_Return, Mod1Mask|ControlMask|ShiftMask, "\033[13;8u", 0, 0}, + { XK_Return, Mod1Mask|ShiftMask, "\033[13;4u", 0, 0}, + { XK_Return, ShiftMask, "\033[13;2u", 0, 0}, + { XK_Pause, ControlMask, "\033[18;5u", 0, 0}, + { XK_Pause, ControlMask|ShiftMask, "\033[18;6u", 0, 0}, + { XK_Pause, Mod1Mask, "\033[18;3u", 0, 0}, + { XK_Pause, Mod1Mask|ControlMask, "\033[18;7u", 0, 0}, + { XK_Pause, Mod1Mask|ControlMask|ShiftMask, "\033[18;8u", 0, 0}, + { XK_Pause, Mod1Mask|ShiftMask, "\033[18;4u", 0, 0}, + { XK_Pause, ShiftMask, "\033[18;2u", 0, 0}, + { XK_Scroll_Lock, ControlMask, "\033[20;5u", 0, 0}, + { XK_Scroll_Lock, ControlMask|ShiftMask, "\033[20;6u", 0, 0}, + { XK_Scroll_Lock, Mod1Mask, "\033[20;3u", 0, 0}, + { XK_Scroll_Lock, Mod1Mask|ControlMask, "\033[20;7u", 0, 0}, + { XK_Scroll_Lock, Mod1Mask|ControlMask|ShiftMask, "\033[20;8u", 0, 0}, + { XK_Scroll_Lock, Mod1Mask|ShiftMask, "\033[20;4u", 0, 0}, + { XK_Scroll_Lock, ShiftMask, "\033[20;2u", 0, 0}, + { XK_Escape, ControlMask, "\033[27;5u", 0, 0}, + { XK_Escape, ControlMask|ShiftMask, "\033[27;6u", 0, 0}, + { XK_Escape, Mod1Mask, "\033[27;3u", 0, 0}, + { XK_Escape, Mod1Mask|ControlMask, "\033[27;7u", 0, 0}, + { XK_Escape, Mod1Mask|ControlMask|ShiftMask, "\033[27;8u", 0, 0}, + { XK_Escape, Mod1Mask|ShiftMask, "\033[27;4u", 0, 0}, + { XK_Escape, ShiftMask, "\033[27;2u", 0, 0}, + { XK_Home, XK_NO_MOD, "\033[H", 0, -1}, + { XK_Home, XK_NO_MOD, "\033[1~", 0, +1}, + { XK_Home, ControlMask|ShiftMask, "\033[80;6u", 0, 0}, + { XK_Home, Mod1Mask, "\033[80;3u", 0, 0}, + { XK_Home, Mod1Mask|ControlMask, "\033[80;7u", 0, 0}, + { XK_Home, Mod1Mask|ControlMask|ShiftMask, "\033[80;8u", 0, 0}, + { XK_Home, Mod1Mask|ShiftMask, "\033[80;4u", 0, 0}, + { XK_End, XK_NO_MOD, "\033[4~", 0, 0}, + { XK_End, ControlMask|ShiftMask, "\033[87;6u", 0, 0}, + { XK_End, Mod1Mask, "\033[87;3u", 0, 0}, + { XK_End, Mod1Mask|ControlMask, "\033[87;7u", 0, 0}, + { XK_End, Mod1Mask|ControlMask|ShiftMask, "\033[87;8u", 0, 0}, + { XK_End, Mod1Mask|ShiftMask, "\033[87;4u", 0, 0}, + { XK_Prior, XK_NO_MOD, "\033[5~", 0, 0}, + { XK_Prior, ControlMask|ShiftMask, "\033[85;6u", 0, 0}, + { XK_Prior, Mod1Mask, "\033[85;3u", 0, 0}, + { XK_Prior, Mod1Mask|ControlMask, "\033[85;7u", 0, 0}, + { XK_Prior, Mod1Mask|ControlMask|ShiftMask, "\033[85;8u", 0, 0}, + { XK_Prior, Mod1Mask|ShiftMask, "\033[85;4u", 0, 0}, + { XK_Next, XK_NO_MOD, "\033[6~", 0, 0}, + { XK_Next, ControlMask|ShiftMask, "\033[86;6u", 0, 0}, + { XK_Next, Mod1Mask, "\033[86;3u", 0, 0}, + { XK_Next, Mod1Mask|ControlMask, "\033[86;7u", 0, 0}, + { XK_Next, Mod1Mask|ControlMask|ShiftMask, "\033[86;8u", 0, 0}, + { XK_Next, Mod1Mask|ShiftMask, "\033[86;4u", 0, 0}, + { XK_Print, ControlMask, "\033[97;5u", 0, 0}, + { XK_Print, ControlMask|ShiftMask, "\033[97;6u", 0, 0}, + { XK_Print, Mod1Mask, "\033[97;3u", 0, 0}, + { XK_Print, Mod1Mask|ControlMask, "\033[97;7u", 0, 0}, + { XK_Print, Mod1Mask|ControlMask|ShiftMask, "\033[97;8u", 0, 0}, + { XK_Print, Mod1Mask|ShiftMask, "\033[97;4u", 0, 0}, + { XK_Print, ShiftMask, "\033[97;2u", 0, 0}, + { XK_Insert, XK_NO_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_NO_MOD, "\033[2~", +1, 0}, + { XK_Insert, ControlMask|ShiftMask, "\033[99;6u", 0, 0}, + { XK_Insert, Mod1Mask, "\033[99;3u", 0, 0}, + { XK_Insert, Mod1Mask|ControlMask, "\033[99;7u", 0, 0}, + { XK_Insert, Mod1Mask|ControlMask|ShiftMask, "\033[99;8u", 0, 0}, + { XK_Insert, Mod1Mask|ShiftMask, "\033[99;4u", 0, 0}, + { XK_Menu, ControlMask, "\033[103;5u", 0, 0}, + { XK_Menu, ControlMask|ShiftMask, "\033[103;6u", 0, 0}, + { XK_Menu, Mod1Mask, "\033[103;3u", 0, 0}, + { XK_Menu, Mod1Mask|ControlMask, "\033[103;7u", 0, 0}, + { XK_Menu, Mod1Mask|ControlMask|ShiftMask, "\033[103;8u", 0, 0}, + { XK_Menu, Mod1Mask|ShiftMask, "\033[103;4u", 0, 0}, + { XK_Menu, ShiftMask, "\033[103;2u", 0, 0}, + { XK_Delete, XK_NO_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_NO_MOD, "\033[3~", +1, 0}, + { XK_Delete, ControlMask|ShiftMask, "\033[255;6u", 0, 0}, + { XK_Delete, Mod1Mask, "\033[255;3u", 0, 0}, + { XK_Delete, Mod1Mask|ControlMask, "\033[255;7u", 0, 0}, + { XK_Delete, Mod1Mask|ControlMask|ShiftMask, "\033[255;8u", 0, 0}, + { XK_Delete, Mod1Mask|ShiftMask, "\033[255;4u", 0, 0}, + { XK_i, ControlMask, "\033[105;5u", 0, 0}, + { XK_i, Mod1Mask|ControlMask, "\033[105;7u", 0, 0}, + { XK_m, ControlMask, "\033[109;5u", 0, 0}, + { XK_m, Mod1Mask|ControlMask, "\033[109;7u", 0, 0}, + { XK_space, ControlMask|ShiftMask, "\033[32;6u", 0, 0}, + { XK_space, Mod1Mask, "\033[32;3u", 0, 0}, + { XK_space, Mod1Mask|ControlMask, "\033[32;7u", 0, 0}, + { XK_space, Mod1Mask|ControlMask|ShiftMask, "\033[32;8u", 0, 0}, + { XK_space, Mod1Mask|ShiftMask, "\033[32;4u", 0, 0}, + { XK_space, ShiftMask, "\033[32;2u", 0, 0}, + { XK_0, ControlMask, "\033[48;5u", 0, 0}, + { XK_A, ControlMask|ShiftMask, "\033[65;6u", 0, 0}, + { XK_B, ControlMask|ShiftMask, "\033[66;6u", 0, 0}, + { XK_C, ControlMask|ShiftMask, "\033[67;6u", 0, 0}, + { XK_D, ControlMask|ShiftMask, "\033[68;6u", 0, 0}, + { XK_E, ControlMask|ShiftMask, "\033[69;6u", 0, 0}, + { XK_F, ControlMask|ShiftMask, "\033[70;6u", 0, 0}, + { XK_G, ControlMask|ShiftMask, "\033[71;6u", 0, 0}, + { XK_H, ControlMask|ShiftMask, "\033[72;6u", 0, 0}, + { XK_I, ControlMask|ShiftMask, "\033[73;6u", 0, 0}, + { XK_I, Mod1Mask|ControlMask|ShiftMask, "\033[73;8u", 0, 0}, + { XK_J, ControlMask|ShiftMask, "\033[75;6u", 0, 0}, + { XK_K, ControlMask|ShiftMask, "\033[74;6u", 0, 0}, + { XK_L, ControlMask|ShiftMask, "\033[76;6u", 0, 0}, + { XK_M, ControlMask|ShiftMask, "\033[77;6u", 0, 0}, + { XK_M, Mod1Mask|ControlMask|ShiftMask, "\033[77;8u", 0, 0}, + { XK_N, ControlMask|ShiftMask, "\033[78;6u", 0, 0}, + { XK_O, ControlMask|ShiftMask, "\033[79;6u", 0, 0}, + { XK_P, ControlMask|ShiftMask, "\033[80;6u", 0, 0}, + { XK_Q, ControlMask|ShiftMask, "\033[81;6u", 0, 0}, + { XK_R, ControlMask|ShiftMask, "\033[82;6u", 0, 0}, + { XK_S, ControlMask|ShiftMask, "\033[83;6u", 0, 0}, + { XK_T, ControlMask|ShiftMask, "\033[84;6u", 0, 0}, + { XK_U, ControlMask|ShiftMask, "\033[85;6u", 0, 0}, + { XK_V, ControlMask|ShiftMask, "\033[86;6u", 0, 0}, + { XK_W, ControlMask|ShiftMask, "\033[87;6u", 0, 0}, + { XK_X, ControlMask|ShiftMask, "\033[88;6u", 0, 0}, + { XK_Y, ControlMask|ShiftMask, "\033[89;6u", 0, 0}, + { XK_Z, ControlMask|ShiftMask, "\033[90;6u", 0, 0}, + { XK_0, Mod1Mask|ControlMask, "\033[48;7u", 0, 0}, + { XK_1, ControlMask, "\033[49;5u", 0, 0}, + { XK_1, Mod1Mask|ControlMask, "\033[49;7u", 0, 0}, + { XK_2, ControlMask, "\033[50;5u", 0, 0}, + { XK_2, Mod1Mask|ControlMask, "\033[50;7u", 0, 0}, + { XK_3, ControlMask, "\033[51;5u", 0, 0}, + { XK_3, Mod1Mask|ControlMask, "\033[51;7u", 0, 0}, + { XK_4, ControlMask, "\033[52;5u", 0, 0}, + { XK_4, Mod1Mask|ControlMask, "\033[52;7u", 0, 0}, + { XK_5, ControlMask, "\033[53;5u", 0, 0}, + { XK_5, Mod1Mask|ControlMask, "\033[53;7u", 0, 0}, + { XK_6, ControlMask, "\033[54;5u", 0, 0}, + { XK_6, Mod1Mask|ControlMask, "\033[54;7u", 0, 0}, + { XK_7, ControlMask, "\033[55;5u", 0, 0}, + { XK_7, Mod1Mask|ControlMask, "\033[55;7u", 0, 0}, + { XK_8, ControlMask, "\033[56;5u", 0, 0}, + { XK_8, Mod1Mask|ControlMask, "\033[56;7u", 0, 0}, + { XK_9, ControlMask, "\033[57;5u", 0, 0}, + { XK_9, Mod1Mask|ControlMask, "\033[57;7u", 0, 0}, + { XK_ampersand, ControlMask, "\033[38;5u", 0, 0}, + { XK_ampersand, ControlMask|ShiftMask, "\033[38;6u", 0, 0}, + { XK_ampersand, Mod1Mask, "\033[38;3u", 0, 0}, + { XK_ampersand, Mod1Mask|ControlMask, "\033[38;7u", 0, 0}, + { XK_ampersand, Mod1Mask|ControlMask|ShiftMask, "\033[38;8u", 0, 0}, + { XK_ampersand, Mod1Mask|ShiftMask, "\033[38;4u", 0, 0}, + { XK_apostrophe, ControlMask, "\033[39;5u", 0, 0}, + { XK_apostrophe, ControlMask|ShiftMask, "\033[39;6u", 0, 0}, + { XK_apostrophe, Mod1Mask, "\033[39;3u", 0, 0}, + { XK_apostrophe, Mod1Mask|ControlMask, "\033[39;7u", 0, 0}, + { XK_apostrophe, Mod1Mask|ControlMask|ShiftMask, "\033[39;8u", 0, 0}, + { XK_apostrophe, Mod1Mask|ShiftMask, "\033[39;4u", 0, 0}, + { XK_asciicircum, ControlMask, "\033[94;5u", 0, 0}, + { XK_asciicircum, ControlMask|ShiftMask, "\033[94;6u", 0, 0}, + { XK_asciicircum, Mod1Mask, "\033[94;3u", 0, 0}, + { XK_asciicircum, Mod1Mask|ControlMask, "\033[94;7u", 0, 0}, + { XK_asciicircum, Mod1Mask|ControlMask|ShiftMask, "\033[94;8u", 0, 0}, + { XK_asciicircum, Mod1Mask|ShiftMask, "\033[94;4u", 0, 0}, + { XK_asciitilde, ControlMask, "\033[126;5u", 0, 0}, + { XK_asciitilde, ControlMask|ShiftMask, "\033[126;6u", 0, 0}, + { XK_asciitilde, Mod1Mask, "\033[126;3u", 0, 0}, + { XK_asciitilde, Mod1Mask|ControlMask, "\033[126;7u", 0, 0}, + { XK_asciitilde, Mod1Mask|ControlMask|ShiftMask, "\033[126;8u", 0, 0}, + { XK_asciitilde, Mod1Mask|ShiftMask, "\033[126;4u", 0, 0}, + { XK_asterisk, ControlMask, "\033[42;5u", 0, 0}, + { XK_asterisk, ControlMask|ShiftMask, "\033[42;6u", 0, 0}, + { XK_asterisk, Mod1Mask, "\033[42;3u", 0, 0}, + { XK_asterisk, Mod1Mask|ControlMask, "\033[42;7u", 0, 0}, + { XK_asterisk, Mod1Mask|ControlMask|ShiftMask, "\033[42;8u", 0, 0}, + { XK_asterisk, Mod1Mask|ShiftMask, "\033[42;4u", 0, 0}, + { XK_at, ControlMask, "\033[64;5u", 0, 0}, + { XK_at, ControlMask|ShiftMask, "\033[64;6u", 0, 0}, + { XK_at, Mod1Mask, "\033[64;3u", 0, 0}, + { XK_at, Mod1Mask|ControlMask, "\033[64;7u", 0, 0}, + { XK_at, Mod1Mask|ControlMask|ShiftMask, "\033[64;8u", 0, 0}, + { XK_at, Mod1Mask|ShiftMask, "\033[64;4u", 0, 0}, + { XK_backslash, ControlMask, "\033[92;5u", 0, 0}, + { XK_backslash, ControlMask|ShiftMask, "\033[92;6u", 0, 0}, + { XK_backslash, Mod1Mask, "\033[92;3u", 0, 0}, + { XK_backslash, Mod1Mask|ControlMask, "\033[92;7u", 0, 0}, + { XK_backslash, Mod1Mask|ControlMask|ShiftMask, "\033[92;8u", 0, 0}, + { XK_backslash, Mod1Mask|ShiftMask, "\033[92;4u", 0, 0}, + { XK_bar, ControlMask, "\033[124;5u", 0, 0}, + { XK_bar, ControlMask|ShiftMask, "\033[124;6u", 0, 0}, + { XK_bar, Mod1Mask, "\033[124;3u", 0, 0}, + { XK_bar, Mod1Mask|ControlMask, "\033[124;7u", 0, 0}, + { XK_bar, Mod1Mask|ControlMask|ShiftMask, "\033[124;8u", 0, 0}, + { XK_bar, Mod1Mask|ShiftMask, "\033[124;4u", 0, 0}, + { XK_braceleft, ControlMask, "\033[123;5u", 0, 0}, + { XK_braceleft, ControlMask|ShiftMask, "\033[123;6u", 0, 0}, + { XK_braceleft, Mod1Mask, "\033[123;3u", 0, 0}, + { XK_braceleft, Mod1Mask|ControlMask, "\033[123;7u", 0, 0}, + { XK_braceleft, Mod1Mask|ControlMask|ShiftMask, "\033[123;8u", 0, 0}, + { XK_braceleft, Mod1Mask|ShiftMask, "\033[123;4u", 0, 0}, + { XK_braceright, ControlMask, "\033[125;5u", 0, 0}, + { XK_braceright, ControlMask|ShiftMask, "\033[125;6u", 0, 0}, + { XK_braceright, Mod1Mask, "\033[125;3u", 0, 0}, + { XK_braceright, Mod1Mask|ControlMask, "\033[125;7u", 0, 0}, + { XK_braceright, Mod1Mask|ControlMask|ShiftMask, "\033[125;8u", 0, 0}, + { XK_braceright, Mod1Mask|ShiftMask, "\033[125;4u", 0, 0}, + { XK_bracketleft, ControlMask, "\033[91;5u", 0, 0}, + { XK_bracketleft, ControlMask|ShiftMask, "\033[91;6u", 0, 0}, + { XK_bracketleft, Mod1Mask, "\033[91;3u", 0, 0}, + { XK_bracketleft, Mod1Mask|ControlMask, "\033[91;7u", 0, 0}, + { XK_bracketleft, Mod1Mask|ControlMask|ShiftMask, "\033[91;8u", 0, 0}, + { XK_bracketleft, Mod1Mask|ShiftMask, "\033[91;4u", 0, 0}, + { XK_bracketright, ControlMask, "\033[93;5u", 0, 0}, + { XK_bracketright, ControlMask|ShiftMask, "\033[93;6u", 0, 0}, + { XK_bracketright, Mod1Mask, "\033[93;3u", 0, 0}, + { XK_bracketright, Mod1Mask|ControlMask, "\033[93;7u", 0, 0}, + { XK_bracketright, Mod1Mask|ControlMask|ShiftMask, "\033[93;8u", 0, 0}, + { XK_bracketright, Mod1Mask|ShiftMask, "\033[93;4u", 0, 0}, + { XK_colon, ControlMask, "\033[58;5u", 0, 0}, + { XK_colon, ControlMask|ShiftMask, "\033[58;6u", 0, 0}, + { XK_colon, Mod1Mask, "\033[58;3u", 0, 0}, + { XK_colon, Mod1Mask|ControlMask, "\033[58;7u", 0, 0}, + { XK_colon, Mod1Mask|ControlMask|ShiftMask, "\033[58;8u", 0, 0}, + { XK_colon, Mod1Mask|ShiftMask, "\033[58;4u", 0, 0}, + { XK_comma, ControlMask, "\033[44;5u", 0, 0}, + { XK_comma, ControlMask|ShiftMask, "\033[44;6u", 0, 0}, + { XK_comma, Mod1Mask, "\033[44;3u", 0, 0}, + { XK_comma, Mod1Mask|ControlMask, "\033[44;7u", 0, 0}, + { XK_comma, Mod1Mask|ControlMask|ShiftMask, "\033[44;8u", 0, 0}, + { XK_comma, Mod1Mask|ShiftMask, "\033[44;4u", 0, 0}, + { XK_dollar, ControlMask, "\033[36;5u", 0, 0}, + { XK_dollar, ControlMask|ShiftMask, "\033[36;6u", 0, 0}, + { XK_dollar, Mod1Mask, "\033[36;3u", 0, 0}, + { XK_dollar, Mod1Mask|ControlMask, "\033[36;7u", 0, 0}, + { XK_dollar, Mod1Mask|ControlMask|ShiftMask, "\033[36;8u", 0, 0}, + { XK_dollar, Mod1Mask|ShiftMask, "\033[36;4u", 0, 0}, + { XK_equal, ControlMask, "\033[61;5u", 0, 0}, + { XK_equal, ControlMask|ShiftMask, "\033[61;6u", 0, 0}, + { XK_equal, Mod1Mask, "\033[61;3u", 0, 0}, + { XK_equal, Mod1Mask|ControlMask, "\033[61;7u", 0, 0}, + { XK_equal, Mod1Mask|ControlMask|ShiftMask, "\033[61;8u", 0, 0}, + { XK_equal, Mod1Mask|ShiftMask, "\033[61;4u", 0, 0}, + { XK_exclam, ControlMask, "\033[33;5u", 0, 0}, + { XK_exclam, ControlMask|ShiftMask, "\033[33;6u", 0, 0}, + { XK_exclam, Mod1Mask, "\033[33;3u", 0, 0}, + { XK_exclam, Mod1Mask|ControlMask, "\033[33;7u", 0, 0}, + { XK_exclam, Mod1Mask|ControlMask|ShiftMask, "\033[33;8u", 0, 0}, + { XK_exclam, Mod1Mask|ShiftMask, "\033[33;4u", 0, 0}, + { XK_grave, ControlMask, "\033[96;5u", 0, 0}, + { XK_grave, ControlMask|ShiftMask, "\033[96;6u", 0, 0}, + { XK_grave, Mod1Mask, "\033[96;3u", 0, 0}, + { XK_grave, Mod1Mask|ControlMask, "\033[96;7u", 0, 0}, + { XK_grave, Mod1Mask|ControlMask|ShiftMask, "\033[96;8u", 0, 0}, + { XK_grave, Mod1Mask|ShiftMask, "\033[96;4u", 0, 0}, + { XK_greater, ControlMask, "\033[62;5u", 0, 0}, + { XK_greater, ControlMask|ShiftMask, "\033[62;6u", 0, 0}, + { XK_greater, Mod1Mask, "\033[62;3u", 0, 0}, + { XK_greater, Mod1Mask|ControlMask, "\033[62;7u", 0, 0}, + { XK_greater, Mod1Mask|ControlMask|ShiftMask, "\033[62;8u", 0, 0}, + { XK_greater, Mod1Mask|ShiftMask, "\033[62;4u", 0, 0}, + { XK_less, ControlMask, "\033[60;5u", 0, 0}, + { XK_less, ControlMask|ShiftMask, "\033[60;6u", 0, 0}, + { XK_less, Mod1Mask, "\033[60;3u", 0, 0}, + { XK_less, Mod1Mask|ControlMask, "\033[60;7u", 0, 0}, + { XK_less, Mod1Mask|ControlMask|ShiftMask, "\033[60;8u", 0, 0}, + { XK_less, Mod1Mask|ShiftMask, "\033[60;4u", 0, 0}, + { XK_minus, ControlMask, "\033[45;5u", 0, 0}, + { XK_minus, ControlMask|ShiftMask, "\033[45;6u", 0, 0}, + { XK_minus, Mod1Mask, "\033[45;3u", 0, 0}, + { XK_minus, Mod1Mask|ControlMask, "\033[45;7u", 0, 0}, + { XK_minus, Mod1Mask|ControlMask|ShiftMask, "\033[45;8u", 0, 0}, + { XK_minus, Mod1Mask|ShiftMask, "\033[45;4u", 0, 0}, + { XK_numbersign, ControlMask, "\033[35;5u", 0, 0}, + { XK_numbersign, ControlMask|ShiftMask, "\033[35;6u", 0, 0}, + { XK_numbersign, Mod1Mask, "\033[35;3u", 0, 0}, + { XK_numbersign, Mod1Mask|ControlMask, "\033[35;7u", 0, 0}, + { XK_numbersign, Mod1Mask|ControlMask|ShiftMask, "\033[35;8u", 0, 0}, + { XK_numbersign, Mod1Mask|ShiftMask, "\033[35;4u", 0, 0}, + { XK_parenleft, ControlMask, "\033[40;5u", 0, 0}, + { XK_parenleft, ControlMask|ShiftMask, "\033[40;6u", 0, 0}, + { XK_parenleft, Mod1Mask, "\033[40;3u", 0, 0}, + { XK_parenleft, Mod1Mask|ControlMask, "\033[40;7u", 0, 0}, + { XK_parenleft, Mod1Mask|ControlMask|ShiftMask, "\033[40;8u", 0, 0}, + { XK_parenleft, Mod1Mask|ShiftMask, "\033[40;4u", 0, 0}, + { XK_parenright, ControlMask, "\033[41;5u", 0, 0}, + { XK_parenright, ControlMask|ShiftMask, "\033[41;6u", 0, 0}, + { XK_parenright, Mod1Mask, "\033[41;3u", 0, 0}, + { XK_parenright, Mod1Mask|ControlMask, "\033[41;7u", 0, 0}, + { XK_parenright, Mod1Mask|ControlMask|ShiftMask, "\033[41;8u", 0, 0}, + { XK_parenright, Mod1Mask|ShiftMask, "\033[41;4u", 0, 0}, + { XK_percent, ControlMask, "\033[37;5u", 0, 0}, + { XK_percent, ControlMask|ShiftMask, "\033[37;6u", 0, 0}, + { XK_percent, Mod1Mask, "\033[37;3u", 0, 0}, + { XK_percent, Mod1Mask|ControlMask, "\033[37;7u", 0, 0}, + { XK_percent, Mod1Mask|ControlMask|ShiftMask, "\033[37;8u", 0, 0}, + { XK_percent, Mod1Mask|ShiftMask, "\033[37;4u", 0, 0}, + { XK_period, ControlMask, "\033[46;5u", 0, 0}, + { XK_period, ControlMask|ShiftMask, "\033[46;6u", 0, 0}, + { XK_period, Mod1Mask|ControlMask, "\033[46;7u", 0, 0}, + { XK_period, Mod1Mask|ControlMask|ShiftMask, "\033[46;8u", 0, 0}, + { XK_period, Mod1Mask|ShiftMask, "\033[46;4u", 0, 0}, + { XK_plus, ControlMask, "\033[43;5u", 0, 0}, + { XK_plus, ControlMask|ShiftMask, "\033[43;6u", 0, 0}, + { XK_plus, Mod1Mask, "\033[43;3u", 0, 0}, + { XK_plus, Mod1Mask|ControlMask, "\033[43;7u", 0, 0}, + { XK_plus, Mod1Mask|ControlMask|ShiftMask, "\033[43;8u", 0, 0}, + { XK_plus, Mod1Mask|ShiftMask, "\033[43;4u", 0, 0}, + { XK_question, ControlMask, "\033[63;5u", 0, 0}, + { XK_question, ControlMask|ShiftMask, "\033[63;6u", 0, 0}, + { XK_question, Mod1Mask, "\033[63;3u", 0, 0}, + { XK_question, Mod1Mask|ControlMask, "\033[63;7u", 0, 0}, + { XK_question, Mod1Mask|ControlMask|ShiftMask, "\033[63;8u", 0, 0}, + { XK_question, Mod1Mask|ShiftMask, "\033[63;4u", 0, 0}, + { XK_quotedbl, ControlMask, "\033[34;5u", 0, 0}, + { XK_quotedbl, ControlMask|ShiftMask, "\033[34;6u", 0, 0}, + { XK_quotedbl, Mod1Mask, "\033[34;3u", 0, 0}, + { XK_quotedbl, Mod1Mask|ControlMask, "\033[34;7u", 0, 0}, + { XK_quotedbl, Mod1Mask|ControlMask|ShiftMask, "\033[34;8u", 0, 0}, + { XK_quotedbl, Mod1Mask|ShiftMask, "\033[34;4u", 0, 0}, + { XK_semicolon, ControlMask, "\033[59;5u", 0, 0}, + { XK_semicolon, ControlMask|ShiftMask, "\033[59;6u", 0, 0}, + { XK_semicolon, Mod1Mask, "\033[59;3u", 0, 0}, + { XK_semicolon, Mod1Mask|ControlMask, "\033[59;7u", 0, 0}, + { XK_semicolon, Mod1Mask|ControlMask|ShiftMask, "\033[59;8u", 0, 0}, + { XK_semicolon, Mod1Mask|ShiftMask, "\033[59;4u", 0, 0}, + { XK_slash, ControlMask|ShiftMask, "\033[47;6u", 0, 0}, + { XK_slash, Mod1Mask, "\033[47;3u", 0, 0}, + { XK_slash, Mod1Mask|ControlMask, "\033[47;7u", 0, 0}, + { XK_slash, Mod1Mask|ControlMask|ShiftMask, "\033[47;8u", 0, 0}, + { XK_slash, Mod1Mask|ShiftMask, "\033[47;4u", 0, 0}, + { XK_underscore, ControlMask, "\033[95;5u", 0, 0}, + { XK_underscore, ControlMask|ShiftMask, "\033[95;6u", 0, 0}, + { XK_underscore, Mod1Mask, "\033[95;3u", 0, 0}, + { XK_underscore, Mod1Mask|ControlMask, "\033[95;7u", 0, 0}, + { XK_underscore, Mod1Mask|ControlMask|ShiftMask, "\033[95;8u", 0, 0}, + { XK_underscore, Mod1Mask|ShiftMask, "\033[95;4u", 0, 0}, +}; diff --git a/patch/font2.c b/patch/font2.c new file mode 100644 index 0000000..d028ea3 --- /dev/null +++ b/patch/font2.c @@ -0,0 +1,96 @@ +int +xloadsparefont(FcPattern *pattern, int flags) +{ + FcPattern *match; + FcResult result; + + match = XftFontMatch(xw.dpy, xw.scr, pattern, &result); + if (!match) { + return 1; + } + + if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(match); + return 1; + } + + frc[frclen].flags = flags; + /* Believe U+0000 glyph will present in each default font */ + frc[frclen].unicodep = 0; + frclen++; + + return 0; +} + +void +xloadsparefonts(void) +{ + FcPattern *pattern; + double fontval; + int fc; + char **fp; + + if (frclen != 0) + die("can't embed spare fonts. cache isn't empty"); + + /* Calculate count of spare fonts */ + fc = sizeof(font2) / sizeof(*font2); + if (fc == 0) + return; + + /* Allocate memory for cache entries. */ + if (frccap < 4 * fc) { + frccap += 4 * fc - frccap; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + for (fp = font2; fp - font2 < fc; ++fp) { + + if (**fp == '-') + pattern = XftXlfdParse(*fp, False, False); + else + pattern = FcNameParse((FcChar8 *)*fp); + + if (!pattern) + die("can't open spare font %s\n", *fp); + + if (defaultfontsize > 0 && defaultfontsize != usedfontsize) { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + fontval *= usedfontsize / defaultfontsize; + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + fontval *= usedfontsize / defaultfontsize; + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_SIZE, fontval); + } + } + + FcPatternAddBool(pattern, FC_SCALABLE, 1); + + + if (xloadsparefont(pattern, FRC_NORMAL)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadsparefont(pattern, FRC_ITALIC)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadsparefont(pattern, FRC_ITALICBOLD)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadsparefont(pattern, FRC_BOLD)) + die("can't open spare font %s\n", *fp); + + FcPatternDestroy(pattern); + } +} diff --git a/patch/font2.h b/patch/font2.h new file mode 100644 index 0000000..69df588 --- /dev/null +++ b/patch/font2.h @@ -0,0 +1,2 @@ +static int xloadsparefont(FcPattern *, int); +static void xloadsparefonts(void); diff --git a/patch/iso14755.c b/patch/iso14755.c new file mode 100644 index 0000000..dbbf2c3 --- /dev/null +++ b/patch/iso14755.c @@ -0,0 +1,21 @@ +void +iso14755(const Arg *arg) +{ + FILE *p; + char *us, *e, codepoint[9], uc[UTF_SIZ]; + unsigned long utf32; + + if (!(p = popen(ISO14755CMD, "r"))) + return; + + us = fgets(codepoint, sizeof(codepoint), p); + pclose(p); + + if (!us || *us == '\0' || *us == '-' || strlen(us) > 7) + return; + if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX || + (*e != '\n' && *e != '\0')) + return; + + ttywrite(uc, utf8encode(utf32, uc), 1); +} diff --git a/patch/iso14755.h b/patch/iso14755.h new file mode 100644 index 0000000..8647a92 --- /dev/null +++ b/patch/iso14755.h @@ -0,0 +1,6 @@ +#define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) + +/* constants */ +#define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: = 0); + return -1; +} + +void +clearurl(void) +{ + while (url_y1 <= url_y2 && url_y1 < term.row) + term.dirty[url_y1++] = 1; + url_y2 = -1; +} + +char * +detecturl(int col, int row, int draw) +{ + static char url[2048]; + int x1, y1, x2, y2, wrapped; + int row_start = row; + int col_start = col; + int i = sizeof(url)/2+1, j = sizeof(url)/2; + int minrow = 0, maxrow = term.row - 1; + url_maxcol = 0; + + /* clear previously underlined url */ + if (draw) + clearurl(); + + if (!isvalidurlchar(TLINEURL(row)[col].u)) + return NULL; + + /* find the first character of url */ + do { + x1 = col_start, y1 = row_start; + url_maxcol = MAX(url_maxcol, x1); + url[--i] = TLINEURL(row_start)[col_start].u; + if (--col_start < 0) { + if (--row_start < minrow || (col_start = findeowl(row_start)) < 0) + break; + } + } while (i > 0 && isvalidurlchar(TLINEURL(row_start)[col_start].u)); + + /* early detection */ + if (url[i] != 'h') + return NULL; + + /* find the last character of url */ + do { + x2 = col, y2 = row; + url_maxcol = MAX(url_maxcol, x2); + url[j++] = TLINEURL(row)[col].u; + wrapped = TLINEURL(row)[col].mode & ATTR_WRAP; + if (++col >= term.col || wrapped) { + col = 0; + if (++row > maxrow || !wrapped) + break; + } + } while (j < sizeof(url)-1 && isvalidurlchar(TLINEURL(row)[col].u)); + + url[j] = 0; + + if (strncmp("https://", &url[i], 8) && strncmp("http://", &url[i], 7)) + return NULL; + + /* underline url (see xdrawglyphfontspecs() in x.c) */ + if (draw) { + url_x1 = (y1 >= 0) ? x1 : 0; + url_x2 = (y2 < term.row) ? x2 : url_maxcol; + url_y1 = MAX(y1, 0); + url_y2 = MIN(y2, term.row-1); + url_draw = 1; + for (y1 = url_y1; y1 <= url_y2; y1++) + term.dirty[y1] = 1; + } + + return &url[i]; +} + +void +openUrlOnClick(int col, int row, char* url_opener) +{ + char *url = detecturl(col, row, 1); + if (url) { + extern char **environ; + pid_t junk; + char *argv[] = { url_opener, url, NULL }; + posix_spawnp(&junk, argv[0], NULL, NULL, argv, environ); + } +} diff --git a/patch/openurlonclick.h b/patch/openurlonclick.h new file mode 100644 index 0000000..5aa09de --- /dev/null +++ b/patch/openurlonclick.h @@ -0,0 +1,8 @@ +#include + +static inline void restoremousecursor(void) { + if (!(win.mode & MODE_MOUSE) && xw.pointerisvisible) + XDefineCursor(xw.dpy, xw.win, xw.vpointer); +} +static void clearurl(void); +static void openUrlOnClick(int col, int row, char* url_opener); diff --git a/patch/rightclicktoplumb_st.c b/patch/rightclicktoplumb_st.c new file mode 100644 index 0000000..da5d173 --- /dev/null +++ b/patch/rightclicktoplumb_st.c @@ -0,0 +1,19 @@ +#if defined(__OpenBSD__) + #include +#endif + +int +subprocwd(char *path) +{ +#if defined(__linux) + if (snprintf(path, PATH_MAX, "/proc/%d/cwd", pid) < 0) + return -1; + return 0; +#elif defined(__OpenBSD__) + size_t sz = PATH_MAX; + int name[3] = {CTL_KERN, KERN_PROC_CWD, pid}; + if (sysctl(name, 3, path, &sz, 0, 0) == -1) + return -1; + return 0; +#endif +} diff --git a/patch/rightclicktoplumb_st.h b/patch/rightclicktoplumb_st.h new file mode 100644 index 0000000..7f8ee04 --- /dev/null +++ b/patch/rightclicktoplumb_st.h @@ -0,0 +1 @@ +int subprocwd(char *); diff --git a/patch/rightclicktoplumb_x.c b/patch/rightclicktoplumb_x.c new file mode 100644 index 0000000..11906f3 --- /dev/null +++ b/patch/rightclicktoplumb_x.c @@ -0,0 +1,24 @@ +#include + +void +plumb(char *sel) { + if (sel == NULL) + return; + char cwd[PATH_MAX]; + pid_t child; + if (subprocwd(cwd) != 0) + return; + + switch(child = fork()) { + case -1: + return; + case 0: + if (chdir(cwd) != 0) + exit(1); + if (execvp(plumb_cmd, (char *const []){plumb_cmd, sel, 0}) == -1) + exit(1); + exit(0); + default: + waitpid(child, NULL, 0); + } +} diff --git a/patch/rightclicktoplumb_x.h b/patch/rightclicktoplumb_x.h new file mode 100644 index 0000000..f77204e --- /dev/null +++ b/patch/rightclicktoplumb_x.h @@ -0,0 +1 @@ +void plumb(char *); diff --git a/patch/sixel_st.c b/patch/sixel_st.c new file mode 100644 index 0000000..1efdf8e --- /dev/null +++ b/patch/sixel_st.c @@ -0,0 +1,55 @@ +sixel_state_t sixel_st; + +void +dcshandle(void) +{ + int bgcolor; + unsigned char r, g, b, a = 255; + + switch (csiescseq.mode[0]) { + default: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case 'q': /* DECSIXEL */ + if (IS_TRUECOL(term.c.attr.bg)) { + r = term.c.attr.bg >> 16 & 255; + g = term.c.attr.bg >> 8 & 255; + b = term.c.attr.bg >> 0 & 255; + } else { + xgetcolor(term.c.attr.bg, &r, &g, &b); + if (term.c.attr.bg == defaultbg) + a = dc.col[defaultbg].pixel >> 24 & 255; + } + bgcolor = a << 24 | b << 16 | g << 8 | r; + if (sixel_parser_init(&sixel_st, 255 << 24, bgcolor, 1, win.cw, win.ch) != 0) + perror("sixel_parser_init() failed"); + term.mode |= MODE_SIXEL; + break; + } +} + +void +scroll_images(int n) { + ImageList *im; + int tmp; + + /* maximum sixel distance in lines from current view before + * deallocation + * TODO: should be in config.h */ + int max_sixel_distance = 10000; + + for (im = term.images; im; im = im->next) { + im->y += n; + + /* check if the current sixel has exceeded the maximum + * draw distance, and should therefore be deleted */ + tmp = im->y; + if (tmp < 0) { tmp = tmp * -1; } + if (tmp > max_sixel_distance) { + fprintf(stderr, "im@0x%08x exceeded maximum distance\n"); + im->should_delete = 1; + } + } +} diff --git a/patch/sixel_st.h b/patch/sixel_st.h new file mode 100644 index 0000000..b1642d4 --- /dev/null +++ b/patch/sixel_st.h @@ -0,0 +1,2 @@ +static void dcshandle(void); +static void scroll_images(int n); diff --git a/patch/sixel_x.c b/patch/sixel_x.c new file mode 100644 index 0000000..0cc7312 --- /dev/null +++ b/patch/sixel_x.c @@ -0,0 +1,14 @@ +void +delete_image(ImageList *im) +{ + if (im->prev) + im->prev->next = im->next; + else + term.images = im->next; + if (im->next) + im->next->prev = im->prev; + if (im->pixmap) + XFreePixmap(xw.dpy, (Drawable)im->pixmap); + free(im->pixels); + free(im); +} diff --git a/patch/st_include.c b/patch/st_include.c new file mode 100644 index 0000000..63d8f4a --- /dev/null +++ b/patch/st_include.c @@ -0,0 +1,6 @@ +/* Patches */ +#include "iso14755.c" +#include "rightclicktoplumb_st.c" +#include "newterm.c" +#include "sixel_st.c" +#include "sync.c" diff --git a/patch/st_include.h b/patch/st_include.h new file mode 100644 index 0000000..b95459e --- /dev/null +++ b/patch/st_include.h @@ -0,0 +1,10 @@ +/* Patches */ +#include "iso14755.h" +#include "openurlonclick.h" +#include "rightclicktoplumb_st.h" +#include "newterm.h" +#include "sixel_st.h" +#include "sync.h" +// #if VIM_BROWSE_PATCH +// #include "normalMode.h" +// #endif diff --git a/patch/sync.c b/patch/sync.c new file mode 100644 index 0000000..9c75c7d --- /dev/null +++ b/patch/sync.c @@ -0,0 +1,31 @@ +#include +struct timespec sutv; + +static void +tsync_begin() +{ + clock_gettime(CLOCK_MONOTONIC, &sutv); + su = 1; +} + +static void +tsync_end() +{ + su = 0; +} + +int +tinsync(uint timeout) +{ + struct timespec now; + if (su && !clock_gettime(CLOCK_MONOTONIC, &now) + && TIMEDIFF(now, sutv) >= timeout) + su = 0; + return su; +} + +int +ttyread_pending() +{ + return twrite_aborted; +} diff --git a/patch/sync.h b/patch/sync.h new file mode 100644 index 0000000..1a84934 --- /dev/null +++ b/patch/sync.h @@ -0,0 +1,7 @@ +static int su = 0; +static int twrite_aborted = 0; + +static void tsync_begin(); +static void tsync_end(); +int tinsync(uint timeout); +int ttyread_pending(); diff --git a/patch/utils.h b/patch/utils.h new file mode 100644 index 0000000..6cfca3e --- /dev/null +++ b/patch/utils.h @@ -0,0 +1,23 @@ +/// Dynamic memory-chunk, with (1) datatype size, (2/3) initialized / allocated chunk, (4) content +typedef struct { uint8_t const elSize; uint32_t init, alloc; char* content; } DynamicArray; +#define UTF8_ARRAY {4, 0, 0, NULL} + +static inline int p_alloc(DynamicArray *s, uint32_t amount) { + uint32_t const diff=s->init+s->elSize*amount-s->alloc, nas=s->alloc+max(diff,15)*s->elSize; + if (s->alloc < s->init + s->elSize * amount) { + char* tmp = realloc(s->content, nas); + if (!tmp) return 0; + s->alloc = nas, s->content = tmp; + } + return 1; +} +static inline char *view(DynamicArray * s, uint32_t i) { return s->content + i*s->elSize; } +static inline char *end(DynamicArray *s, uint32_t i) { return s->content +s->init-(i+1)*s->elSize; } +static inline uint32_t getU32(DynamicArray* s, uint32_t i, int b) { return *((uint32_t*) (b ?view(s,i) :end(s,i))); } +static char *expand(DynamicArray *s) { if (!p_alloc(s, 1)) return NULL; s->init += s->elSize; return end(s, 0); } +static inline void pop(DynamicArray* s) { s->init -= s->elSize; } +static inline void empty(DynamicArray* s) { s->init = 0; } +static inline int size(DynamicArray const * s) { return s->init / s->elSize; } +static inline void assign(DynamicArray* s, DynamicArray const *o) { + if (p_alloc(s, size(o))) memcpy(s->content, o->content, (s->init=o->init)); +} diff --git a/patch/x_include.c b/patch/x_include.c new file mode 100644 index 0000000..6b09b31 --- /dev/null +++ b/patch/x_include.c @@ -0,0 +1,9 @@ +/* Patches */ +#include "alpha.c" +#include "boxdraw.c" +#include "fixkeyboardinput.c" +#include "font2.c" +#include "openurlonclick.c" +#include "rightclicktoplumb_x.c" +#include "sixel_x.c" +#include "xresources.c" diff --git a/patch/x_include.h b/patch/x_include.h new file mode 100644 index 0000000..c885dd4 --- /dev/null +++ b/patch/x_include.h @@ -0,0 +1,6 @@ +/* Patches */ +#include "alpha.h" +#include "boxdraw.h" +#include "font2.h" +#include "rightclicktoplumb_x.h" +#include "xresources.h" diff --git a/patch/xresources.c b/patch/xresources.c new file mode 100644 index 0000000..8926d93 --- /dev/null +++ b/patch/xresources.c @@ -0,0 +1,78 @@ +int +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) +{ + char **sdst = dst; + int *idst = dst; + float *fdst = dst; + + char fullname[256]; + char fullclass[256]; + char *type; + XrmValue ret; + + snprintf(fullname, sizeof(fullname), "%s.%s", + opt_name ? opt_name : "st", name); + snprintf(fullclass, sizeof(fullclass), "%s.%s", + opt_class ? opt_class : "St", name); + fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; + + XrmGetResource(db, fullname, fullclass, &type, &ret); + if (ret.addr == NULL || strncmp("String", type, 64)) + return 1; + + switch (rtype) { + case STRING: + *sdst = ret.addr; + break; + case INTEGER: + *idst = strtoul(ret.addr, NULL, 10); + break; + case FLOAT: + *fdst = strtof(ret.addr, NULL); + break; + } + return 0; +} + +void +config_init(Display *dpy) +{ + char *resm; + XrmDatabase db; + ResourcePref *p; + + XrmInitialize(); + resm = XResourceManagerString(dpy); + if (!resm) + return; + + db = XrmGetStringDatabase(resm); + for (p = resources; p < resources + LEN(resources); p++) + resource_load(db, p->name, p->type, p->dst); +} + +void +reload_config(int sig) +{ + /* Recreate a Display object to have up to date Xresources entries */ + Display *dpy; + if (!(dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + + config_init(dpy); + xloadcols(); + + /* nearly like zoomabs() */ + xunloadfonts(); + xloadfonts(font, 0); /* font <- config_init() */ + xloadsparefonts(); + cresize(0, 0); + redraw(); + xhints(); + + XCloseDisplay(dpy); + + /* from https://st.suckless.org/patches/xresources-with-reload-signal */ + /* triggers re-render if we're visible */ + ttywrite("\033[O", 3, 1); +} diff --git a/patch/xresources.h b/patch/xresources.h new file mode 100644 index 0000000..c184852 --- /dev/null +++ b/patch/xresources.h @@ -0,0 +1,17 @@ +#include + +/* Xresources preferences */ +enum resource_type { + STRING = 0, + INTEGER = 1, + FLOAT = 2 +}; + +typedef struct { + char *name; + enum resource_type type; + void *dst; +} ResourcePref; + +int resource_load(XrmDatabase, char *, enum resource_type, void *); +void config_init(Display *dpy); diff --git a/sixel.c b/sixel.c new file mode 100644 index 0000000..d11fbf8 --- /dev/null +++ b/sixel.c @@ -0,0 +1,601 @@ +// sixel.c (part of mintty) +// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c) +// Licensed under the terms of the GNU General Public License v3 or later. + +#include +#include /* memcpy */ + +#include "sixel.h" +#include "sixel_hls.h" + +#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16) + (255 << 24)) +#define SIXEL_PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m)) +#define SIXEL_XRGB(r,g,b) SIXEL_RGB(SIXEL_PALVAL(r, 255, 100), SIXEL_PALVAL(g, 255, 100), SIXEL_PALVAL(b, 255, 100)) + +static sixel_color_t const sixel_default_color_table[] = { + SIXEL_XRGB( 0, 0, 0), /* 0 Black */ + SIXEL_XRGB(20, 20, 80), /* 1 Blue */ + SIXEL_XRGB(80, 13, 13), /* 2 Red */ + SIXEL_XRGB(20, 80, 20), /* 3 Green */ + SIXEL_XRGB(80, 20, 80), /* 4 Magenta */ + SIXEL_XRGB(20, 80, 80), /* 5 Cyan */ + SIXEL_XRGB(80, 80, 20), /* 6 Yellow */ + SIXEL_XRGB(53, 53, 53), /* 7 Gray 50% */ + SIXEL_XRGB(26, 26, 26), /* 8 Gray 25% */ + SIXEL_XRGB(33, 33, 60), /* 9 Blue* */ + SIXEL_XRGB(60, 26, 26), /* 10 Red* */ + SIXEL_XRGB(33, 60, 33), /* 11 Green* */ + SIXEL_XRGB(60, 33, 60), /* 12 Magenta* */ + SIXEL_XRGB(33, 60, 60), /* 13 Cyan* */ + SIXEL_XRGB(60, 60, 33), /* 14 Yellow* */ + SIXEL_XRGB(80, 80, 80), /* 15 Gray 75% */ +}; + +static int +set_default_color(sixel_image_t *image) +{ + int i; + int n; + int r; + int g; + int b; + + /* palette initialization */ + for (n = 1; n < 17; n++) { + image->palette[n] = sixel_default_color_table[n - 1]; + } + + /* colors 17-232 are a 6x6x6 color cube */ + for (r = 0; r < 6; r++) { + for (g = 0; g < 6; g++) { + for (b = 0; b < 6; b++) { + image->palette[n++] = SIXEL_RGB(r * 51, g * 51, b * 51); + } + } + } + + /* colors 233-256 are a grayscale ramp, intentionally leaving out */ + for (i = 0; i < 24; i++) { + image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11); + } + + for (; n < DECSIXEL_PALETTE_MAX; n++) { + image->palette[n] = SIXEL_RGB(255, 255, 255); + } + + return (0); +} + +static int +sixel_image_init( + sixel_image_t *image, + int width, + int height, + int fgcolor, + int bgcolor, + int use_private_register) +{ + int status = (-1); + size_t size; + + size = (size_t)(width * height) * sizeof(sixel_color_no_t); + image->width = width; + image->height = height; + image->data = (sixel_color_no_t *)malloc(size); + image->ncolors = 2; + image->use_private_register = use_private_register; + + if (image->data == NULL) { + status = (-1); + goto end; + } + memset(image->data, 0, size); + + image->palette[0] = bgcolor; + + if (image->use_private_register) + image->palette[1] = fgcolor; + + image->palette_modified = 0; + + status = (0); + +end: + return status; +} + + +static int +image_buffer_resize( + sixel_image_t *image, + int width, + int height) +{ + int status = (-1); + size_t size; + sixel_color_no_t *alt_buffer; + int n; + int min_height; + + size = (size_t)(width * height) * sizeof(sixel_color_no_t); + alt_buffer = (sixel_color_no_t *)malloc(size); + if (alt_buffer == NULL) { + /* free source image */ + free(image->data); + image->data = NULL; + status = (-1); + goto end; + } + + min_height = height > image->height ? image->height: height; + if (width > image->width) { /* if width is extended */ + for (n = 0; n < min_height; ++n) { + /* copy from source image */ + memcpy(alt_buffer + width * n, + image->data + image->width * n, + (size_t)image->width * sizeof(sixel_color_no_t)); + /* fill extended area with background color */ + memset(alt_buffer + width * n + image->width, + 0, + (size_t)(width - image->width) * sizeof(sixel_color_no_t)); + } + } else { + for (n = 0; n < min_height; ++n) { + /* copy from source image */ + memcpy(alt_buffer + width * n, + image->data + image->width * n, + (size_t)width * sizeof(sixel_color_no_t)); + } + } + + if (height > image->height) { /* if height is extended */ + /* fill extended area with background color */ + memset(alt_buffer + width * image->height, + 0, + (size_t)(width * (height - image->height)) * sizeof(sixel_color_no_t)); + } + + /* free source image */ + free(image->data); + + image->data = alt_buffer; + image->width = width; + image->height = height; + + status = (0); + +end: + return status; +} + +static void +sixel_image_deinit(sixel_image_t *image) +{ + free(image->data); + image->data = NULL; +} + +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) +{ + int status = (-1); + + st->state = PS_DECSIXEL; + st->pos_x = 0; + st->pos_y = 0; + st->max_x = 0; + st->max_y = 0; + st->attributed_pan = 2; + st->attributed_pad = 1; + st->attributed_ph = 0; + st->attributed_pv = 0; + st->repeat_count = 1; + st->color_index = 16; + st->grid_width = cell_width; + st->grid_height = cell_height; + st->nparams = 0; + st->param = 0; + + /* buffer initialization */ + status = sixel_image_init(&st->image, 1, 1, fgcolor, bgcolor, use_private_register); + + return status; +} + +int +sixel_parser_set_default_color(sixel_state_t *st) +{ + return set_default_color(&st->image); +} + +int +sixel_parser_finalize(sixel_state_t *st, unsigned char **pixels) +{ + int status = (-1); + sixel_image_t *image = &st->image; + int x, y; + sixel_color_no_t *src; + unsigned char *dst; + int color; + int w, h; + + if (++st->max_x < st->attributed_ph) + st->max_x = st->attributed_ph; + + if (++st->max_y < st->attributed_pv) + st->max_y = st->attributed_pv; + + if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) { + status = set_default_color(image); + if (status < 0) + goto end; + } + + w = st->max_x < image->width ? st->max_x : image->width; + h = st->max_y < image->height ? st->max_y : image->height; + + *pixels = malloc(w * h * 4); + if (*pixels == NULL) + goto end; + + src = st->image.data; + dst = *pixels; + for (y = 0; y < h; y++) { + src = st->image.data + image->width * y; + for (x = 0; x < w; ++x) { + color = st->image.palette[*src++]; + *dst++ = color >> 16 & 0xff; /* b */ + *dst++ = color >> 8 & 0xff; /* g */ + *dst++ = color >> 0 & 0xff; /* r */ + *dst++ = color >> 24 & 0xff; /* a */ + } + } + + image->width = w; + image->height = h; + + status = (0); + +end: + return status; +} + +/* convert sixel data into indexed pixel bytes and palette data */ +int +sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len) +{ + int status = (-1); + int n; + int i; + int x; + int y; + int bits; + int sixel_vertical_mask; + int sx; + int sy; + int c; + int pos; + unsigned char *p0 = p; + sixel_image_t *image = &st->image; + + if (! image->data) + goto end; + + while (p < p0 + len) { + switch (st->state) { + case PS_ESC: + goto end; + + case PS_DECSIXEL: + switch (*p) { + case '\x1b': + st->state = PS_ESC; + p++; + break; + case '"': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGRA; + p++; + break; + case '!': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGRI; + p++; + break; + case '#': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGCI; + p++; + break; + case '$': + /* DECGCR Graphics Carriage Return */ + st->pos_x = 0; + p++; + break; + case '-': + /* DECGNL Graphics Next Line */ + st->pos_x = 0; + if (st->pos_y < DECSIXEL_HEIGHT_MAX - 5 - 6) + st->pos_y += 6; + else + st->pos_y = DECSIXEL_HEIGHT_MAX + 1; + p++; + break; + default: + if (*p >= '?' && *p <= '~') { /* sixel characters */ + if ((image->width < (st->pos_x + st->repeat_count) || image->height < (st->pos_y + 6)) + && image->width < DECSIXEL_WIDTH_MAX && image->height < DECSIXEL_HEIGHT_MAX) { + sx = image->width * 2; + sy = image->height * 2; + while (sx < (st->pos_x + st->repeat_count) || sy < (st->pos_y + 6)) { + sx *= 2; + sy *= 2; + } + + if (sx > DECSIXEL_WIDTH_MAX) + sx = DECSIXEL_WIDTH_MAX; + if (sy > DECSIXEL_HEIGHT_MAX) + sy = DECSIXEL_HEIGHT_MAX; + + status = image_buffer_resize(image, sx, sy); + if (status < 0) + goto end; + } + + if (st->color_index > image->ncolors) + image->ncolors = st->color_index; + + if (st->pos_x + st->repeat_count > image->width) + st->repeat_count = image->width - st->pos_x; + + if (st->repeat_count > 0 && st->pos_y + 5 < image->height) { + bits = *p - '?'; + if (bits != 0) { + sixel_vertical_mask = 0x01; + if (st->repeat_count <= 1) { + for (i = 0; i < 6; i++) { + if ((bits & sixel_vertical_mask) != 0) { + pos = image->width * (st->pos_y + i) + st->pos_x; + image->data[pos] = st->color_index; + if (st->max_x < st->pos_x) + st->max_x = st->pos_x; + if (st->max_y < (st->pos_y + i)) + st->max_y = st->pos_y + i; + } + sixel_vertical_mask <<= 1; + } + } else { + /* st->repeat_count > 1 */ + for (i = 0; i < 6; i++) { + if ((bits & sixel_vertical_mask) != 0) { + c = sixel_vertical_mask << 1; + for (n = 1; (i + n) < 6; n++) { + if ((bits & c) == 0) + break; + c <<= 1; + } + for (y = st->pos_y + i; y < st->pos_y + i + n; ++y) { + for (x = st->pos_x; x < st->pos_x + st->repeat_count; ++x) + image->data[image->width * y + x] = st->color_index; + } + if (st->max_x < (st->pos_x + st->repeat_count - 1)) + st->max_x = st->pos_x + st->repeat_count - 1; + if (st->max_y < (st->pos_y + i + n - 1)) + st->max_y = st->pos_y + i + n - 1; + i += (n - 1); + sixel_vertical_mask <<= (n - 1); + } + sixel_vertical_mask <<= 1; + } + } + } + } + if (st->repeat_count > 0) + st->pos_x += st->repeat_count; + st->repeat_count = 1; + } + p++; + break; + } + break; + + case PS_DECGRA: + /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + p++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + st->param = DECSIXEL_PARAMVALUE_MAX; + p++; + break; + case ';': + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + p++; + break; + default: + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + if (st->nparams > 0) + st->attributed_pad = st->params[0]; + if (st->nparams > 1) + st->attributed_pan = st->params[1]; + if (st->nparams > 2 && st->params[2] > 0) + st->attributed_ph = st->params[2]; + if (st->nparams > 3 && st->params[3] > 0) + st->attributed_pv = st->params[3]; + + if (st->attributed_pan <= 0) + st->attributed_pan = 1; + if (st->attributed_pad <= 0) + st->attributed_pad = 1; + + if (image->width < st->attributed_ph || + image->height < st->attributed_pv) { + sx = st->attributed_ph; + if (image->width > st->attributed_ph) + sx = image->width; + + sy = st->attributed_pv; + if (image->height > st->attributed_pv) + sy = image->height; + + /* the height of the image buffer must be divisible by 6 + * to avoid unnecessary resizing of the image buffer in + * sixel_parser_parse() */ + sy = (sy + 5) / 6 * 6; + + if (sx > DECSIXEL_WIDTH_MAX) + sx = DECSIXEL_WIDTH_MAX; + if (sy > DECSIXEL_HEIGHT_MAX) + sy = DECSIXEL_HEIGHT_MAX; + + status = image_buffer_resize(image, sx, sy); + if (status < 0) + goto end; + } + st->state = PS_DECSIXEL; + st->param = 0; + st->nparams = 0; + } + break; + + case PS_DECGRI: + /* DECGRI Graphics Repeat Introducer ! Pn Ch */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + p++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + st->param = DECSIXEL_PARAMVALUE_MAX; + p++; + break; + default: + st->repeat_count = st->param; + if (st->repeat_count == 0) + st->repeat_count = 1; + st->state = PS_DECSIXEL; + st->param = 0; + st->nparams = 0; + break; + } + break; + + case PS_DECGCI: + /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + p++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + st->param = DECSIXEL_PARAMVALUE_MAX; + p++; + break; + case ';': + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + p++; + break; + default: + st->state = PS_DECSIXEL; + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + + if (st->nparams > 0) { + st->color_index = 1 + st->params[0]; /* offset 1(background color) added */ + if (st->color_index < 0) + st->color_index = 0; + else if (st->color_index >= DECSIXEL_PALETTE_MAX) + st->color_index = DECSIXEL_PALETTE_MAX - 1; + } + + if (st->nparams > 4) { + st->image.palette_modified = 1; + if (st->params[1] == 1) { + /* HLS */ + if (st->params[2] > 360) + st->params[2] = 360; + if (st->params[3] > 100) + st->params[3] = 100; + if (st->params[4] > 100) + st->params[4] = 100; + image->palette[st->color_index] + = hls_to_rgb(st->params[2], st->params[3], st->params[4]); + } else if (st->params[1] == 2) { + /* RGB */ + if (st->params[2] > 100) + st->params[2] = 100; + if (st->params[3] > 100) + st->params[3] = 100; + if (st->params[4] > 100) + st->params[4] = 100; + image->palette[st->color_index] + = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]); + } + } + break; + } + break; + default: + break; + } + } + + status = (0); + +end: + return status; +} + +void +sixel_parser_deinit(sixel_state_t *st) +{ + if (st) + sixel_image_deinit(&st->image); +} diff --git a/sixel.h b/sixel.h new file mode 100644 index 0000000..a7d403d --- /dev/null +++ b/sixel.h @@ -0,0 +1,58 @@ +#ifndef SIXEL_H +#define SIXEL_H + +#define DECSIXEL_PARAMS_MAX 16 +#define DECSIXEL_PALETTE_MAX 1024 +#define DECSIXEL_PARAMVALUE_MAX 65535 +#define DECSIXEL_WIDTH_MAX 4096 +#define DECSIXEL_HEIGHT_MAX 4096 + +typedef unsigned short sixel_color_no_t; +typedef unsigned int sixel_color_t; + +typedef struct sixel_image_buffer { + sixel_color_no_t *data; + int width; + int height; + sixel_color_t palette[DECSIXEL_PALETTE_MAX]; + sixel_color_no_t ncolors; + int palette_modified; + int use_private_register; +} sixel_image_t; + +typedef enum parse_state { + PS_ESC = 1, /* ESC */ + PS_DECSIXEL = 2, /* DECSIXEL body part ", $, -, ? ... ~ */ + PS_DECGRA = 3, /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ + PS_DECGRI = 4, /* DECGRI Graphics Repeat Introducer ! Pn Ch */ + PS_DECGCI = 5, /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ +} parse_state_t; + +typedef struct parser_context { + parse_state_t state; + int pos_x; + int pos_y; + int max_x; + int max_y; + int attributed_pan; + int attributed_pad; + int attributed_ph; + int attributed_pv; + int repeat_count; + int color_index; + int bgindex; + int grid_width; + int grid_height; + int param; + int nparams; + int params[DECSIXEL_PARAMS_MAX]; + sixel_image_t image; +} sixel_state_t; + +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); +int sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len); +int sixel_parser_set_default_color(sixel_state_t *st); +int sixel_parser_finalize(sixel_state_t *st, unsigned char **pixels); +void sixel_parser_deinit(sixel_state_t *st); + +#endif diff --git a/sixel_hls.c b/sixel_hls.c new file mode 100644 index 0000000..c88241c --- /dev/null +++ b/sixel_hls.c @@ -0,0 +1,115 @@ +// sixel.c (part of mintty) +// this function is derived from a part of graphics.c +// in Xterm pl#310 originally written by Ross Combs. +// +// Copyright 2013,2014 by Ross Combs +// +// All Rights Reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name(s) of the above copyright +// holders shall not be used in advertising or otherwise to promote the +// sale, use or other dealings in this Software without prior written +// authorization. + +#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16) + (255 << 24)) + +int +hls_to_rgb(int hue, int lum, int sat) +{ + double hs = (hue + 240) % 360; + double hv = hs / 360.0; + double lv = lum / 100.0; + double sv = sat / 100.0; + double c, x, m, c2; + double r1, g1, b1; + int r, g, b; + int hpi; + + if (sat == 0) { + r = g = b = lum * 255 / 100; + return SIXEL_RGB(r, g, b); + } + + if ((c2 = ((2.0 * lv) - 1.0)) < 0.0) { + c2 = -c2; + } + c = (1.0 - c2) * sv; + hpi = (int) (hv * 6.0); + x = (hpi & 1) ? c : 0.0; + m = lv - 0.5 * c; + + switch (hpi) { + case 0: + r1 = c; + g1 = x; + b1 = 0.0; + break; + case 1: + r1 = x; + g1 = c; + b1 = 0.0; + break; + case 2: + r1 = 0.0; + g1 = c; + b1 = x; + break; + case 3: + r1 = 0.0; + g1 = x; + b1 = c; + break; + case 4: + r1 = x; + g1 = 0.0; + b1 = c; + break; + case 5: + r1 = c; + g1 = 0.0; + b1 = x; + break; + default: + return SIXEL_RGB(255, 255, 255); + } + + r = (int) ((r1 + m) * 100.0 + 0.5); + g = (int) ((g1 + m) * 100.0 + 0.5); + b = (int) ((b1 + m) * 100.0 + 0.5); + + if (r < 0) { + r = 0; + } else if (r > 100) { + r = 100; + } + if (g < 0) { + g = 0; + } else if (g > 100) { + g = 100; + } + if (b < 0) { + b = 0; + } else if (b > 100) { + b = 100; + } + return SIXEL_RGB(r * 255 / 100, g * 255 / 100, b * 255 / 100); +} diff --git a/sixel_hls.h b/sixel_hls.h new file mode 100644 index 0000000..6176589 --- /dev/null +++ b/sixel_hls.h @@ -0,0 +1,7 @@ +/* + * Primary color hues: + * blue: 0 degrees + * red: 120 degrees + * green: 240 degrees + */ +int hls_to_rgb(int hue, int lum, int sat); diff --git a/st.1 b/st.1 new file mode 100644 index 0000000..39120b4 --- /dev/null +++ b/st.1 @@ -0,0 +1,177 @@ +.TH ST 1 st\-VERSION +.SH NAME +st \- simple terminal +.SH SYNOPSIS +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-l +.IR line ] +.RB [ \-w +.IR windowid ] +.RB [[ \-e ] +.IR command +.RI [ arguments ...]] +.PP +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-w +.IR windowid ] +.RB \-l +.IR line +.RI [ stty_args ...] +.SH DESCRIPTION +.B st +is a simple terminal emulator. +.SH OPTIONS +.TP +.B \-a +disable alternate screens in terminal +.TP +.BI \-c " class" +defines the window class (default $TERM). +.TP +.BI \-f " font" +defines the +.I font +to use when st is run. +.TP +.BI \-g " geometry" +defines the X11 geometry string. +The form is [=][{xX}][{+-}{+-}]. See +.BR XParseGeometry (3) +for further details. +.TP +.B \-i +will fixate the position given with the -g option. +.TP +.BI \-n " name" +defines the window instance name (default $TERM). +.TP +.BI \-o " iofile" +writes all the I/O to +.I iofile. +This feature is useful when recording st sessions. A value of "-" means +standard output. +.TP +.BI \-T " title" +defines the window title (default 'st'). +.TP +.BI \-t " title" +defines the window title (default 'st'). +.TP +.BI \-w " windowid" +embeds st within the window identified by +.I windowid +.TP +.BI \-l " line" +use a tty +.I line +instead of a pseudo terminal. +.I line +should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port +0). +When this flag is given +remaining arguments are used as flags for +.BR stty(1). +By default st initializes the serial line to 8 bits, no parity, 1 stop bit +and a 38400 baud rate. The speed is set by appending it as last argument +(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are +.BR stty(1) +flags. If you want to set odd parity on 115200 baud use for example 'st -l +/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for +example 'st -l /dev/ttyS0 cs7 115200'. See +.BR stty(1) +for more arguments and cases. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +.BI \-e " command " [ " arguments " "... ]" +st executes +.I command +instead of the shell. If this is used it +.B must be the last option +on the command line, as in xterm / rxvt. +This option is only intended for compatibility, +and all the remaining arguments are used as a command +even without it. +.SH SHORTCUTS +.TP +.B Break +Send a break in the serial line. +Break key is obtained in PC keyboards +pressing at the same time control and pause. +.TP +.B Ctrl-Print Screen +Toggle if st should print to the +.I iofile. +.TP +.B Shift-Print Screen +Print the full screen to the +.I iofile. +.TP +.B Print Screen +Print the selection to the +.I iofile. +.TP +.B Ctrl-Shift-Page Up +Increase font size. +.TP +.B Ctrl-Shift-Page Down +Decrease font size. +.TP +.B Ctrl-Shift-Home +Reset to default font size. +.TP +.B Ctrl-Shift-y +Paste from primary selection (middle mouse button). +.TP +.B Ctrl-Shift-c +Copy the selected text to the clipboard selection. +.TP +.B Ctrl-Shift-v +Paste from the clipboard selection. +.SH CUSTOMIZATION +.B st +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1), +.BR utmp (1), +.BR stty (1), +.BR scroll (1) +.SH BUGS +See the TODO file in the distribution. + diff --git a/st.c b/st.c new file mode 100644 index 0000000..0cb3083 --- /dev/null +++ b/st.c @@ -0,0 +1,2898 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + + + +#include "sixel.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define CAR_PER_ARG 4 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, + MODE_SIXEL = 1 << 7, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, + ESC_DCS =128, +}; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; + int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */ +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void readcolonargs(char **, int, int[][CAR_PER_ARG]); +static void csiparse(void); +static void csireset(void); +static void osc4_color_response(int num); +static void osc_color_response(int index, int num); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; +sixel_state_t sixel_st; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + + +#include "patch/st_include.h" + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode & ATTR_WRAP)) + { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode & ATTR_WRAP)) + { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) + { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ( + (y < sel.ne.y || lastx >= linelen) + && (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + setenv("COLORTERM", "truecolor", 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc ps exec", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += twrite_aborted ? 0 : ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(void) +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +int +tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsync_end(); + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + ImageList *im; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } + for (im = term.images; im; im = im->next) + im->should_delete = 1; +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + ImageList *im = term.images; + + term.line = term.alt; + term.alt = tmp; + term.images = term.images_alt; + term.images_alt = im; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + restoremousecursor(); + + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + /* move images, if they are inside the scrolling region */ + ImageList *im; + for (im = term.images; im; im = im->next) { + if (im->y * win.ch + im->height > orig * win.ch && im->y <= term.bot) { + im->y += n; + im->should_delete |= (im->y >= term.row); + } + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + restoremousecursor(); + + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + scroll_images(-1 * n); + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG]) +{ + int i = 0; + for (; i < CAR_PER_ARG; i++) + params[cursor][i] = -1; + + if (**p != ':') + return; + + char *np = NULL; + i = 0; + + while (**p == ':' && i < CAR_PER_ARG) { + while (**p == ':') + (*p)++; + params[cursor][i] = strtol(*p, &np, 10); + *p = np; + i++; + } +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; + + if (isboxdraw(u)) + term.line[y][x].mode |= ATTR_BOXDRAW; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + term.c.attr.ustyle = -1; + term.c.attr.ucolor[0] = -1; + term.c.attr.ucolor[1] = -1; + term.c.attr.ucolor[2] = -1; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.ustyle = csiescseq.carg[i][0]; + + if (term.c.attr.ustyle != 0) + term.c.attr.mode |= ATTR_UNDERLINE; + else + term.c.attr.mode &= ~ATTR_UNDERLINE; + + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + case 58: + term.c.attr.ucolor[0] = csiescseq.carg[i][1]; + term.c.attr.ucolor[1] = csiescseq.carg[i][2]; + term.c.attr.ucolor[2] = csiescseq.carg[i][3]; + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; + break; + case 59: + term.c.attr.ucolor[0] = -1; + term.c.attr.ucolor[1] = -1; + term.c.attr.ucolor[2] = -1; + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; + const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buffer[40]; + int len; + ImageList *im; + int maxcol = term.col; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, maxcol-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, maxcol-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* screen */ + + tclearregion(0, 0, maxcol-1, term.row-1); + + for (im = term.images; im; im = im->next) + im->should_delete = 1; + break; + case 3: /* scrollback */ + if (!IS_SET(MODE_ALTSCREEN)) { + for (im = term.images; im; im = im->next) + im->should_delete |= (im->y * win.ch + im->height <= 0); + } + break; + case 6: /* sixels */ + for (im = term.images; im; im = im->next) + im->should_delete = 1; + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, maxcol-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, maxcol-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + if (IS_SET(MODE_ALTSCREEN)) { + for (im = term.images; im; im = im->next) + im->should_delete = 1; + } + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buffer, sizeof(buffer), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buffer, len, 0); + break; + default: + goto unknown; + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 't': /* title stack operations */ + switch (csiescseq.arg[0]) { + case 22: /* pust current title on stack */ + switch (csiescseq.arg[1]) { + case 0: + case 1: + case 2: + xpushtitle(); + break; + default: + goto unknown; + } + break; + case 23: /* pop last title from stack */ + switch (csiescseq.arg[1]) { + case 0: + case 1: + case 2: + xsettitle(NULL, 1); + break; + default: + goto unknown; + } + break; + default: + goto unknown; + } + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + ImageList *new_image; + int i; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1], 0); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1], 0); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 8: /* Clear Hyperlinks */ + return; + case 10: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + tfulldirt(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + tfulldirt(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + tfulldirt(); + return; + case 4: /* color set */ + if ((par == 4 && narg < 3) || narg < 2) + break; + p = strescseq.args[((par == 4) ? 2 : 1)]; + /* FALLTHROUGH */ + case 104: /* color reset */ + if (par == 10) + j = defaultfg; + else if (par == 11) + j = defaultbg; + else if (par == 12) + j = defaultcs; + else + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + if (j == defaultbg) + xclearwin(); + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0], 0); + return; + case 'P': /* DCS -- Device Control String */ + if (IS_SET(MODE_SIXEL)) { + term.mode &= ~MODE_SIXEL; + new_image = malloc(sizeof(ImageList)); + memset(new_image, 0, sizeof(ImageList)); + if (sixel_parser_finalize(&sixel_st, &new_image->pixels) != 0) { + perror("sixel_parser_finalize() failed"); + sixel_parser_deinit(&sixel_st); + free(new_image); + return; + } + new_image->x = term.c.x; + new_image->y = term.c.y; + new_image->width = sixel_st.image.width; + new_image->height = sixel_st.image.height; + sixel_parser_deinit(&sixel_st); + if (term.images) { + ImageList *im; + for (im = term.images; im->next;) + im = im->next; + im->next = new_image; + new_image->prev = im; + } else { + term.images = new_image; + } + for (i = 0; i < (sixel_st.image.height + win.ch-1)/win.ch; ++i) { + int x; + tclearregion(term.c.x, term.c.y, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw-1, term.c.y); + for (x = term.c.x; x < MIN(term.col, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw); x++) + term.line[term.c.y][x].mode |= ATTR_SIXEL; + tnewline(0); + } + } + /* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */ + if (strstr(strescseq.buf, "=1s") == strescseq.buf) + tsync_begin(); /* BSU */ + else if (strstr(strescseq.buf, "=2s") == strescseq.buf) + tsync_end(); /* ESU */ + return; + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + strreset(); + + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + term.esc |= ESC_DCS; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + term.esc |= ESC_DCS; + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + xfreetitlestack(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) + { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (IS_SET(MODE_SIXEL)) { + if (sixel_parser_parse(&sixel_st, (unsigned char *)&u, 1) != 0) + perror("sixel_parser_parse() failed"); + return; + } + if (term.esc & ESC_DCS) + goto check_control_code; + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_DCS) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + csiparse(); + dcshandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + int su0 = su; + twrite_aborted = 0; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) + { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (su0 && !su) { + twrite_aborted = 1; + break; // ESU - allow rendering before a new BSU + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) + { + bp = term.tabs + term.col; + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL, 0); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +#include "patch/st_include.c" + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + + drawregion(0, 0, term.col, term.row); + + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx], + term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.desktop b/st.desktop new file mode 100644 index 0000000..14a389a --- /dev/null +++ b/st.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=st +Comment=st is a simple terminal implementation for X +Exec=st +Icon=utilities-terminal +Terminal=false +Type=Application +Categories=System;TerminalEmulator; \ No newline at end of file diff --git a/st.h b/st.h new file mode 100644 index 0000000..66343d1 --- /dev/null +++ b/st.h @@ -0,0 +1,318 @@ +/* See LICENSE for license details. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \ + (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOXDRAW = 1 << 11, + ATTR_LIGA = 1 << 12, + ATTR_SIXEL = 1 << 13, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + ATTR_DIRTYUNDERLINE = 1 << 15, +}; + +typedef struct _ImageList { + struct _ImageList *next, *prev; + unsigned char *pixels; + void *pixmap; + int width; + int height; + int x; + int y; + int should_delete; +} ImageList; + +enum drawing_mode { + DRAW_NONE = 0, + DRAW_BG = 1 << 0, + DRAW_FG = 1 << 1, +}; + +/* Used to control which screen(s) keybindings and mouse shortcuts apply to. */ +enum screen { + S_PRI = -1, /* primary screen */ + S_ALL = 0, /* both primary and alt screen */ + S_ALT = 1 /* alternate screen */ +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ + int ustyle; /* underline style */ + int ucolor[3]; /* underline color */ +} Glyph; + +typedef Glyph *Line; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + ImageList *images; /* sixel images */ + ImageList *images_alt; /* sixel images for alternate screen */ + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int hborderpx, vborderpx; + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + /* Here, we use the term *pointer* to differentiate the cursor + * one sees when hovering the mouse over the terminal from, e.g., + * a green rectangle where text would be entered. */ + Cursor vpointer, bpointer; /* visible and hidden pointers */ + int pointerisvisible; + Cursor upointer; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; + int screen; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; + int screen; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +void die(const char *, ...); +void redraw(void); +void draw(void); +void drawregion(int, int, int, int); +void tfulldirt(void); + +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +int tisaltscr(void); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + +int isboxdraw(Rune); +ushort boxdrawindex(const Glyph *); +#ifdef XFT_VERSION +/* only exposed to x.c, otherwise we'll need Xft.h for the types */ +void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); +void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); +#endif // XFT_VERSION + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; + +extern const int boxdraw, boxdraw_bold, boxdraw_braille; +extern float alpha; +extern float alphaUnfocused; + +extern DC dc; +extern XWindow xw; +extern XSelection xsel; +extern TermWindow win; +extern Term term; diff --git a/st.info b/st.info new file mode 100644 index 0000000..b286fb5 --- /dev/null +++ b/st.info @@ -0,0 +1,252 @@ +st-mono| simpleterm monocolor, +# undercurl patch / UNDERCURL_PATCH + Su, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + E3=\E[3J, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, +# CSI 22, 23 patch / CSI_22_23_PATCH +# rmcup=\E[?1049l, + rmcup=\E[?1049l\E[23;0;0t, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, +# CSI 22, 23 patch / CSI_22_23_PATCH +# smcup=\E[?1049h, + smcup=\E[?1049h\E[22;0;0t, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, + BE=\E[?2004h, + BD=\E[?2004l, + PS=\E[200~, + PE=\E[201~, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, +# sync patch / SYNC_PATCH + Sync=\EP=%p1%ds\E\\, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + 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, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/win.h b/win.h new file mode 100644 index 0000000..1eff487 --- /dev/null +++ b/win.h @@ -0,0 +1,43 @@ +/* See LICENSE for license details. */ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, const char *); +void xseticontitle(char *); +void xfreetitlestack(void); +void xsettitle(char *, int); +void xpushtitle(void); +int xsetcursor(int); +void xsetmode(int, unsigned int); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); +void xclearwin(void); diff --git a/x.c b/x.c new file mode 100644 index 0000000..13dd9b2 --- /dev/null +++ b/x.c @@ -0,0 +1,2763 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" +#include "hb.h" + +#include + +/* Undercurl slope types */ +enum undercurl_slope_type { + UNDERCURL_SLOPE_ASCENDING = 0, + UNDERCURL_SLOPE_TOP_CAP = 1, + UNDERCURL_SLOPE_DESCENDING = 2, + UNDERCURL_SLOPE_BOTTOM_CAP = 3 +}; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void ttysend(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); + +#include "patch/st_include.h" +#include "patch/x_include.h" + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* size of title stack */ +#define TITLESTACKSIZE 8 + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); +static void xresetfontsettings(ushort mode, Font **font, int *frcflags); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void sigusr1_reload(int sig); +static int mouseaction(XEvent *, uint); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +Term term; +DC dc; +XWindow xw; +XSelection xsel; +TermWindow win; + +static int tstki; /* title stack index */ +static char *titlestack[TITLESTACKSIZE]; /* title stack */ + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; +static char *opt_dir = NULL; + +static int focused = 0; + +static uint buttons; /* bit field of pressed buttons */ +static int cursorblinks = 0; +static Cursor cursor; +static XColor xmousefg, xmousebg; + +#include "patch/x_include.c" + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + xloadsparefonts(); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - win.hborderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - win.vborderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + int screen = tisaltscr() ? S_ALT : S_PRI; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (!ms->screen || (ms->screen == screen)) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + + clearurl(); + url_click = 1; + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } + +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) + { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); + + clipcopy(NULL); +} + +void +sigusr1_reload(int sig) +{ + reload_config(sig); + signal(SIGUSR1, sigusr1_reload); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) { + mousesel(e, 1); + if (url_click && e->xkey.state & url_opener_modkey) + openUrlOnClick(evcol(e), evrow(e), url_opener); + } + else if (btn == Button3) + plumb(xsel.primary); +} + +void +bmotion(XEvent *e) +{ + if (!xw.pointerisvisible) { + if (win.mode & MODE_MOUSE) + XUndefineCursor(xw.dpy, xw.win); + else + XDefineCursor(xw.dpy, xw.win, xw.vpointer); + xw.pointerisvisible = 1; + if (!IS_SET(MODE_MOUSEMANY)) + xsetpointermotion(0); + } + if (!IS_SET(MODE_MOUSE)) { + if (!(e->xbutton.state & Button1Mask) && detecturl(evcol(e), evrow(e), 1)) + XDefineCursor(xw.dpy, xw.win, xw.upointer); + else + XDefineCursor(xw.dpy, xw.win, xw.vpointer); + } + url_click = 0; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + win.hborderpx = (win.w - col * win.cw) / 2; + win.vborderpx = (win.h - row * win.ch) / 2; + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth + ); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + + +void +xloadalpha(void) +{ + float const usedAlpha = focused ? alpha : alphaUnfocused; + if (opt_alpha) alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24; +} + +void +xloadcols(void) +{ + static int loaded; + Color *cp; + + if (!loaded) { + dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256)); + dc.col = xmalloc((dc.collen) * sizeof(Color)); + } + + for (int i = 0; i+1 < dc.collen; ++i) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + if (dc.collen) // cannot die, as the color is already loaded. + xloadcolor(focused ? bg : bgUnfocused, NULL, &dc.col[defaultbg]); + + xloadalpha(); + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + /* set alpha value of bg color */ + if (x == defaultbg) { + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xclearwin(void) +{ + xclear(0, 0, win.w, win.h); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : "st", + opt_class ? opt_class : "St"}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = 1; + sizeh->width_inc = 1; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = XftFontMatch(xw.dpy, xw.scr, pattern, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Clear Harfbuzz font cache. */ + hbunloadfonts(); + + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Pixmap blankpm; + Window parent; + pid_t thispid = getpid(); + XWindowAttributes attr; + XVisualInfo vis; + + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* spare fonts */ + xloadsparefonts(); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * win.hborderpx + cols * win.cw; + win.h = 2 * win.vborderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask + ; + xw.attrs.colormap = xw.cmap; + xw.attrs.event_mask |= PointerMotionMask; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + xw.pointerisvisible = 1; + xw.vpointer = XcursorLibraryLoadCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, xw.vpointer); + + + blankpm = XCreateBitmapFromData(xw.dpy, xw.win, &(char){0}, 1, 1); + xw.bpointer = XCreatePixmapCursor(xw.dpy, blankpm, blankpm, + &xmousefg, &xmousebg, 0, 0); + + xw.upointer = XCreateFontCursor(xw.dpy, XC_hand2); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; + + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); +} + +void +xresetfontsettings(ushort mode, Font **font, int *frcflags) +{ + *font = &dc.font; + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + *font = &dc.ibfont; + *frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + *font = &dc.ifont; + *frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + *font = &dc.bfont; + *frcflags = FRC_BOLD; + } +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + int length = 0, start = 0; + HbTransformData shaped = { 0 }; + + /* Initial values. */ + mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; + xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) + { + /* Fetch rune and mode for current glyph. */ + mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ + if (mode & ATTR_WDUMMY) + continue; + + if ( + prevmode != mode + || ATTRCMP(glyphs[start], glyphs[i]) + || selected(x + i, y) != selected(x + start, y) + || i == (len - 1) + ) { + /* Handle 1-character wide segments and end of line */ + length = i - start; + if (i == start) { + length = 1; + } else if (i == (len - 1)) { + length = (i - start + 1); + } + + /* Shape the segment. */ + hbtransform(&shaped, font->match, glyphs, start, length); + for (int code_idx = 0; code_idx < shaped.count; code_idx++) { + rune = glyphs[start + code_idx].u; + runewidth = win.cw * ((glyphs[start + code_idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); + + if (glyphs[start + code_idx].mode & ATTR_WDUMMY) + continue; + + if (glyphs[start + code_idx].mode & ATTR_BOXDRAW) { + /* minor shoehorning: boxdraw uses only this ushort */ + specs[numspecs].font = font->match; + specs[numspecs].glyph = boxdrawindex(&glyphs[start + code_idx]); + specs[numspecs].x = xp; + specs[numspecs].y = yp; + xp += runewidth; + numspecs++; + } else + if (shaped.glyphs[code_idx].codepoint != 0) { + /* If symbol is found, put it into the specs. */ + specs[numspecs].font = font->match; + specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; + specs[numspecs].x = xp + (short)shaped.positions[code_idx].x_offset; + specs[numspecs].y = yp + (short)shaped.positions[code_idx].y_offset; + xp += runewidth; + numspecs++; + } else { + /* If it's not found, try to fetch it through the font cache. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + } + + /* Cleanup and get ready for next segment. */ + hbcleanup(&shaped); + start = i; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + xresetfontsettings(mode, &font, &frcflags); + yp = winy + font->ascent; + } + } + } + + return numspecs; +} + +static int isSlopeRising (int x, int iPoint, int waveWidth) +{ + // . . . . + // / \ / \ / \ / \ + // / \ / \ / \ / \ + // . . . . . + + // Find absolute `x` of point + x += iPoint * (waveWidth/2); + + // Find index of absolute wave + int absSlope = x / ((float)waveWidth/2); + + return (absSlope % 2); +} + +static int getSlope (int x, int iPoint, int waveWidth) +{ + // Sizes: Caps are half width of slopes + // 1_2 1_2 1_2 1_2 + // / \ / \ / \ / \ + // / \ / \ / \ / \ + // 0 3_0 3_0 3_0 3_ + // <2-> <1> <---6----> + + // Find type of first point + int firstType; + x -= (x / waveWidth) * waveWidth; + if (x < (waveWidth * (2.f/6.f))) + firstType = UNDERCURL_SLOPE_ASCENDING; + else if (x < (waveWidth * (3.f/6.f))) + firstType = UNDERCURL_SLOPE_TOP_CAP; + else if (x < (waveWidth * (5.f/6.f))) + firstType = UNDERCURL_SLOPE_DESCENDING; + else + firstType = UNDERCURL_SLOPE_BOTTOM_CAP; + + // Find type of given point + int pointType = (iPoint % 4); + pointType += firstType; + pointType %= 4; + + return pointType; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int dmode) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch; + int width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + if (bg == fg) { + bg = &dc.col[defaultfg]; + fg = &dc.col[defaultbg]; + } else { + temp = fg; + fg = bg; + bg = temp; + } + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + + + if (dmode & DRAW_BG) { + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, win.hborderpx, + winy + win.ch + + ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); + } + if (winx + width >= win.hborderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, win.vborderpx); + if (winy + win.ch >= win.vborderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Fill the background */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + } + + if (dmode & DRAW_FG) { + if (base.mode & ATTR_BOXDRAW) { + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + } else { + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + } + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + // Underline Color + const int widthThreshold = 28; // +1 width every widthThreshold px of font + int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width + int linecolor; + if ((base.ucolor[0] >= 0) && + !(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) && + !(base.mode & ATTR_INVISIBLE) + ) { + // Special color for underline + // Index + if (base.ucolor[1] < 0) { + linecolor = dc.col[base.ucolor[0]].pixel; + } + // RGB + else { + XColor lcolor; + lcolor.red = base.ucolor[0] * 257; + lcolor.green = base.ucolor[1] * 257; + lcolor.blue = base.ucolor[2] * 257; + lcolor.flags = DoRed | DoGreen | DoBlue; + XAllocColor(xw.dpy, xw.cmap, &lcolor); + linecolor = lcolor.pixel; + } + } else { + // Foreground color for underline + linecolor = fg->pixel; + } + + XGCValues ugcv = { + .foreground = linecolor, + .line_width = wlw, + .line_style = LineSolid, + .cap_style = CapNotLast + }; + + GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), + GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, + &ugcv); + + // Underline Style + if (base.ustyle != 3) { + XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx, + winy + dc.font.ascent * chscale + 1, width, wlw); + } else if (base.ustyle == 3) { + int ww = win.cw;//width; + int wh = dc.font.descent - wlw/2 - 1;//r.height/7; + int wx = winx; + int wy = winy + win.ch - dc.font.descent; + +#if UNDERCURL_STYLE == UNDERCURL_CURLY + // Draw waves + int narcs = charlen * 2 + 1; + XArc *arcs = xmalloc(sizeof(XArc) * narcs); + + int i = 0; + for (i = 0; i < charlen-1; i++) { + arcs[i*2] = (XArc) { + .x = wx + win.cw * i + ww / 4, + .y = wy, + .width = win.cw / 2, + .height = wh, + .angle1 = 0, + .angle2 = 180 * 64 + }; + arcs[i*2+1] = (XArc) { + .x = wx + win.cw * i + ww * 0.75, + .y = wy, + .width = win.cw/2, + .height = wh, + .angle1 = 180 * 64, + .angle2 = 180 * 64 + }; + } + // Last wave + arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh, + 0, 180 * 64 }; + // Last wave tail + arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.), + wh, 180 * 64, 90 * 64}; + // First wave tail + i++; + arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64, + 90 * 64 }; + + XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs); + + free(arcs); +#elif UNDERCURL_STYLE == UNDERCURL_SPIKY + // Make the underline corridor larger + /* + wy -= wh; + */ + wh *= 2; + + // Set the angle of the slope to 45° + ww = wh; + + // Position of wave is independent of word, it's absolute + wx = (wx / (ww/2)) * (ww/2); + + int marginStart = winx - wx; + + // Calculate number of points with floating precision + float n = width; // Width of word in pixels + n = (n / ww) * 2; // Number of slopes (/ or \) + n += 2; // Add two last points + int npoints = n; // Convert to int + + // Total length of underline + float waveLength = 0; + + if (npoints >= 3) { + // We add an aditional slot in case we use a bonus point + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); + + // First point (Starts with the word bounds) + points[0] = (XPoint) { + .x = wx + marginStart, + .y = (isSlopeRising(wx, 0, ww)) + ? (wy - marginStart + ww/2.f) + : (wy + marginStart) + }; + + // Second point (Goes back to the absolute point coordinates) + points[1] = (XPoint) { + .x = (ww/2.f) - marginStart, + .y = (isSlopeRising(wx, 1, ww)) + ? (ww/2.f - marginStart) + : (-ww/2.f + marginStart) + }; + waveLength += (ww/2.f) - marginStart; + + // The rest of the points + for (int i = 2; i < npoints-1; i++) { + points[i] = (XPoint) { + .x = ww/2, + .y = (isSlopeRising(wx, i, ww)) + ? wh/2 + : -wh/2 + }; + waveLength += ww/2; + } + + // Last point + points[npoints-1] = (XPoint) { + .x = ww/2, + .y = (isSlopeRising(wx, npoints-1, ww)) + ? wh/2 + : -wh/2 + }; + waveLength += ww/2; + + // End + if (waveLength < width) { // Add a bonus point? + int marginEnd = width - waveLength; + points[npoints] = (XPoint) { + .x = marginEnd, + .y = (isSlopeRising(wx, npoints, ww)) + ? (marginEnd) + : (-marginEnd) + }; + + npoints++; + } else if (waveLength > width) { // Is last point too far? + int marginEnd = waveLength - width; + points[npoints-1].x -= marginEnd; + if (isSlopeRising(wx, npoints-1, ww)) + points[npoints-1].y -= (marginEnd); + else + points[npoints-1].y += (marginEnd); + } + + // Draw the lines + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, + CoordModePrevious); + + // Draw a second underline with an offset of 1 pixel + if ( ((win.ch / (widthThreshold/2)) % 2)) { + points[0].x++; + + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, + npoints, CoordModePrevious); + } + + // Free resources + free(points); + } +#else // UNDERCURL_CAPPED + // Cap is half of wave width + float capRatio = 0.5f; + + // Make the underline corridor larger + wh *= 2; + + // Set the angle of the slope to 45° + ww = wh; + ww *= 1 + capRatio; // Add a bit of width for the cap + + // Position of wave is independent of word, it's absolute + wx = (wx / ww) * ww; + + float marginStart; + switch(getSlope(winx, 0, ww)) { + case UNDERCURL_SLOPE_ASCENDING: + marginStart = winx - wx; + break; + case UNDERCURL_SLOPE_TOP_CAP: + marginStart = winx - (wx + (ww * (2.f/6.f))); + break; + case UNDERCURL_SLOPE_DESCENDING: + marginStart = winx - (wx + (ww * (3.f/6.f))); + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + marginStart = winx - (wx + (ww * (5.f/6.f))); + break; + } + + // Calculate number of points with floating precision + float n = width; // Width of word in pixels + // ._. + n = (n / ww) * 4; // Number of points (./ \.) + n += 2; // Add two last points + int npoints = n; // Convert to int + + // Position of the pen to draw the lines + float penX = 0; + float penY = 0; + + if (npoints >= 3) { + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); + + // First point (Starts with the word bounds) + penX = winx; + switch (getSlope(winx, 0, ww)) { + case UNDERCURL_SLOPE_ASCENDING: + penY = wy + wh/2.f - marginStart; + break; + case UNDERCURL_SLOPE_TOP_CAP: + penY = wy; + break; + case UNDERCURL_SLOPE_DESCENDING: + penY = wy + marginStart; + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + penY = wy + wh/2.f; + break; + } + points[0].x = penX; + points[0].y = penY; + + // Second point (Goes back to the absolute point coordinates) + switch (getSlope(winx, 1, ww)) { + case UNDERCURL_SLOPE_ASCENDING: + penX += ww * (1.f/6.f) - marginStart; + penY += 0; + break; + case UNDERCURL_SLOPE_TOP_CAP: + penX += ww * (2.f/6.f) - marginStart; + penY += -wh/2.f + marginStart; + break; + case UNDERCURL_SLOPE_DESCENDING: + penX += ww * (1.f/6.f) - marginStart; + penY += 0; + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + penX += ww * (2.f/6.f) - marginStart; + penY += -marginStart + wh/2.f; + break; + } + points[1].x = penX; + points[1].y = penY; + + // The rest of the points + for (int i = 2; i < npoints; i++) { + switch (getSlope(winx, i, ww)) { + case UNDERCURL_SLOPE_ASCENDING: + case UNDERCURL_SLOPE_DESCENDING: + penX += ww * (1.f/6.f); + penY += 0; + break; + case UNDERCURL_SLOPE_TOP_CAP: + penX += ww * (2.f/6.f); + penY += -wh / 2.f; + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + penX += ww * (2.f/6.f); + penY += wh / 2.f; + break; + } + points[i].x = penX; + points[i].y = penY; + } + + // End + float waveLength = penX - winx; + if (waveLength < width) { // Add a bonus point? + int marginEnd = width - waveLength; + penX += marginEnd; + switch(getSlope(winx, npoints, ww)) { + case UNDERCURL_SLOPE_ASCENDING: + case UNDERCURL_SLOPE_DESCENDING: + //penY += 0; + break; + case UNDERCURL_SLOPE_TOP_CAP: + penY += -marginEnd; + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + penY += marginEnd; + break; + } + + points[npoints].x = penX; + points[npoints].y = penY; + + npoints++; + } else if (waveLength > width) { // Is last point too far? + int marginEnd = waveLength - width; + points[npoints-1].x -= marginEnd; + switch(getSlope(winx, npoints-1, ww)) { + case UNDERCURL_SLOPE_TOP_CAP: + points[npoints-1].y += marginEnd; + break; + case UNDERCURL_SLOPE_BOTTOM_CAP: + points[npoints-1].y -= marginEnd; + break; + default: + break; + } + } + + // Draw the lines + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, + CoordModeOrigin); + + // Draw a second underline with an offset of 1 pixel + if ( ((win.ch / (widthThreshold/2)) % 2)) { + for (int i = 0; i < npoints; i++) + points[i].x++; + + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, + npoints, CoordModeOrigin); + } + + // Free resources + free(points); + } +#endif + } + + XFreeGC(xw.dpy, ugc); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + } + + if (url_draw && y >= url_y1 && y <= url_y2) { + int x1 = (y == url_y1) ? url_x1 : 0; + int x2 = (y == url_y2) ? MIN(url_x2, term.col-1) : url_maxcol; + if (x + charlen > x1 && x <= x2) { + int xu = MAX(x, x1); + int wu = (x2 - xu + 1) * win.cw; + xu = win.hborderpx + xu * win.cw; + XftDrawRect(xw.draw, fg, xu, winy + dc.font.ascent * chscale + 2, wu, 1); + url_draw = (y != url_y2 || x + charlen <= x2); + } + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y, DRAW_BG | DRAW_FG); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + /* Redraw the line where cursor was previously. + * It will restore the ligatures broken by the cursor. */ + xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE) || !IS_SET(MODE_FOCUSED)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } + else { + g.fg = defaultbg; + g.bg = defaultcs; + } + + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 0: /* Blinking block */ + case 1: /* Blinking block (default) */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 2: /* Steady block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking underline */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 4: /* Steady underline */ + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + cursorthickness, win.ch); + break; + case 7: /* Blinking st cursor */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 8: /* Steady st cursor */ + g.u = stcursor; + xdrawglyph(g, cx, cy); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + (cx + 1) * win.cw - 1, + win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p, int pop) +{ + XTextProperty prop; + + free(titlestack[tstki]); + if (pop) { + titlestack[tstki] = NULL; + tstki = (tstki - 1 + TITLESTACKSIZE) % TITLESTACKSIZE; + p = titlestack[tstki] ? titlestack[tstki] : opt_title; + } else if (p) { + titlestack[tstki] = xstrdup(p); + } else { + titlestack[tstki] = NULL; + p = opt_title; + } + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +void +xpushtitle(void) +{ + int tstkin = (tstki + 1) % TITLESTACKSIZE; + + free(titlestack[tstkin]); + titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL; + tstki = tstkin; +} + +void +xfreetitlestack(void) +{ + for (int i = 0; i < LEN(titlestack); i++) { + free(titlestack[i]); + titlestack[i] = NULL; + } +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + int numspecs_cached; + Glyph base, new; + XftGlyphFontSpec *specs; + + numspecs_cached = xmakeglyphfontspecs(xw.specbuf, &line[x1], x2 - x1, x1, y1); + + /* Draw line in 2 passes: background and foreground. This way wide glyphs + won't get truncated (#223) */ + for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) { + specs = xw.specbuf; + numspecs = numspecs_cached; + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1, dmode); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1, dmode); + } +} + +void +xfinishdraw(void) +{ + ImageList *im, *next; + XGCValues gcvalues; + GC gc; + + for (im = term.images; im; im = next) { + /* get the next image here, because delete_image() will delete the current image */ + next = im->next; + + if (im->should_delete) { + delete_image(im); + continue; + } + + if (!im->pixmap) { + im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, im->width, im->height, + xw.depth + ); + XImage ximage = { + .format = ZPixmap, + .data = (char *)im->pixels, + .width = im->width, + .height = im->height, + .xoffset = 0, + .byte_order = LSBFirst, + .bitmap_bit_order = MSBFirst, + .bits_per_pixel = 32, + .bytes_per_line = im->width * 4, + .bitmap_unit = 32, + .bitmap_pad = 32, + .depth = xw.depth + }; + XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, im->width, im->height); + free(im->pixels); + im->pixels = NULL; + } + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); + + XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, im->width, im->height, win.hborderpx + im->x * win.cw, win.vborderpx + im->y * win.ch); + XFreeGC(xw.dpy, gc); + + } + + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + if (!set && !xw.pointerisvisible) + return; + set = 1; /* keep MotionNotify event enabled */ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((flags & MODE_MOUSE) + && xw.pointerisvisible + ) { + if (win.mode & MODE_MOUSE) + XUndefineCursor(xw.dpy, xw.win); + else + XDefineCursor(xw.dpy, xw.win, xw.vpointer); + } + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */ + return 1; + win.cursor = (cursor ? cursor : cursorstyle); + cursorblinks = win.cursor == 0 || win.cursor == 1 || + win.cursor == 3 || win.cursor == 5 || + win.cursor == 7; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + if (!focused) { + focused = 1; + xloadcols(); + tfulldirt(); + } + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + if (focused) { + focused = 0; + xloadcols(); + tfulldirt(); + } + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len, screen; + Rune c; + Status status; + Shortcut *bp; + + if (xw.pointerisvisible) { + int x = e->x - win.hborderpx; + int y = e->y - win.vborderpx; + LIMIT(x, 0, win.tw - 1); + LIMIT(y, 0, win.th - 1); + if (!detecturl(x / win.cw, y / win.ch, 0)) { + XDefineCursor(xw.dpy, xw.win, xw.bpointer); + xsetpointermotion(1); + xw.pointerisvisible = 0; + } + } + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + + screen = tisaltscr() ? S_ALT : S_PRI; + + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state) && + (!bp->screen || bp->screen == screen)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy) || ttyread_pending()) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending(); + if (ttyin) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (ttyin || xev) + { + if (!drawing) { + trigger = now; + if (IS_SET(MODE_BLINK)) { + win.mode ^= MODE_BLINK; + } + lastblink = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + if (tinsync(su_timeout)) { + /* + * on synchronized-update draw-suspension: don't reset + * drawing so that we draw ASAP once we can (just after + * ESU). it won't be too soon because we already can + * draw now but we skip. we set timeout > 0 to draw on + * SU-timeout even without new content. + */ + timeout = minlatency; + continue; + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) + { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class]" + " [-d path]" + " [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class]" + " [-d path]" + " [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorstyle); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'd': + opt_dir = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + signal(SIGUSR1, sigusr1_reload); + if (!(xw.dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + + config_init(xw.dpy); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + defaultbg = MAX(LEN(colorname), 256); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + if (opt_dir && chdir(opt_dir)) + die("Can't change to working directory %s\n", opt_dir); + run(); + + return 0; +}