From 6890e2ca91444ebac1c0d176154787642c3cd70e Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Tue, 12 Dec 2023 18:56:48 +0100
Subject: [PATCH 01/13] initial commit

---
 FAQ                              | 253 +++++++++
 LEGACY                           |  17 +
 Makefile                         |  16 +-
 README                           |  34 ++
 TODO                             |  28 +
 boxdraw.c                        | 194 -------
 boxdraw_data.h                   | 214 --------
 config.h => config.def.h         | 131 ++---
 config.mk                        |   6 +-
 hb.c                             | 124 -----
 hb.h                             |  14 -
 patches/st-scrollback-0.8.5.diff | 350 ------------
 st.1                             |   8 -
 st.c                             | 234 ++------
 st.h                             |  20 +-
 st.info                          |   9 +-
 win.h                            |   6 +-
 x.c                              | 904 +++++++------------------------
 18 files changed, 629 insertions(+), 1933 deletions(-)
 create mode 100644 FAQ
 create mode 100644 LEGACY
 create mode 100644 README
 create mode 100644 TODO
 delete mode 100644 boxdraw.c
 delete mode 100644 boxdraw_data.h
 rename config.h => config.def.h (88%)
 delete mode 100644 hb.c
 delete mode 100644 hb.h
 delete mode 100644 patches/st-scrollback-0.8.5.diff

diff --git a/FAQ b/FAQ
new file mode 100644
index 0000000..6287a27
--- /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
+<http://zsh.sourceforge.net/FAQ/zshfaq03.html#l25>:
+
+	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
+<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html#tag_20_11_13>.
+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
+<https://lists.suckless.org/dev/1404/20697.html>. 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
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/Makefile b/Makefile
index adfa07a..15db421 100644
--- a/Makefile
+++ b/Makefile
@@ -4,16 +4,10 @@
 
 include config.mk
 
-SRC = st.c x.c boxdraw.c hb.c
+SRC = st.c x.c
 OBJ = $(SRC:.c=.o)
 
-all: options st
-
-options:
-	@echo st build options:
-	@echo "CFLAGS  = $(STCFLAGS)"
-	@echo "LDFLAGS = $(STLDFLAGS)"
-	@echo "CC      = $(CC)"
+all: st
 
 config.h:
 	cp config.def.h config.h
@@ -22,9 +16,7 @@ config.h:
 	$(CC) $(STCFLAGS) -c $<
 
 st.o: config.h st.h win.h
-x.o: arg.h config.h st.h win.h hb.h
-boxdraw.o: config.h st.h boxdraw_data.h
-hb.o: st.h
+x.o: arg.h config.h st.h win.h
 
 $(OBJ): config.h config.mk
 
@@ -56,4 +48,4 @@ uninstall:
 	rm -f $(DESTDIR)$(PREFIX)/bin/st
 	rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
 
-.PHONY: all options clean dist install uninstall
+.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 <aurelien dot aptel at gmail dot com> 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/boxdraw.c b/boxdraw.c
deleted file mode 100644
index 28a92d0..0000000
--- a/boxdraw.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
- * MIT/X Consortium License
- */
-
-#include <X11/Xft/Xft.h>
-#include "st.h"
-#include "boxdraw_data.h"
-
-/* 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/boxdraw_data.h b/boxdraw_data.h
deleted file mode 100644
index 7890500..0000000
--- a/boxdraw_data.h
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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/config.h b/config.def.h
similarity index 88%
rename from config.h
rename to config.def.h
index 70d9775..91ab8ca 100644
--- a/config.h
+++ b/config.def.h
@@ -1,14 +1,12 @@
 /* See LICENSE file for copyright and license details. */
 
-static char *font = "FiraCode Nerd Font Mono:pixelsize=14";
-static int borderpx = 2;
-
-/* Underline curl style
- * CURLY 0
- * SPIKY 1
- * CAPPED 2
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
  */
-#define UNDERCURL_STYLE 2
+static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
+static int borderpx = 2;
 
 /*
  * What program is execed by st depends of these precedence rules:
@@ -55,32 +53,20 @@ int allowwindowops = 0;
  * 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;
+static double minlatency = 8;
+static double maxlatency = 33;
 
 /*
  * blinking timeout (set to 0 to disable blinking) for the terminal blinking
  * attribute.
  */
-static unsigned int blinktimeout = 600;
+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 = 0;
-
 /*
  * bell volume. It must be a value between -100 and 100. Use 0 for disabling
  * it
@@ -110,61 +96,52 @@ unsigned int tabspaces = 8;
 /* Terminal colors (16 first used in escape sequence) */
 static const char *colorname[] = {
 	/* 8 normal colors */
-	"#15161E",
-	"#f7768e",
-	"#9ece6a",
-	"#e0af68",
-	"#7aa2f7",
-	"#bb9af7",
-	"#7dcfff",
-	"#a9b1d6",
+	"black",
+	"red3",
+	"green3",
+	"yellow3",
+	"blue2",
+	"magenta3",
+	"cyan3",
+	"gray90",
 
 	/* 8 bright colors */
-	"#414868",
-	"#f7768e",
-	"#9ece6a",
-	"#e0af68",
-	"#7aa2f7",
-	"#bb9af7",
-	"#7dcfff",
-	"#c0caf5",
+	"gray50",
+	"red",
+	"green",
+	"yellow",
+	"#5c5cff",
+	"magenta",
+	"cyan",
+	"white",
 
 	[255] = 0,
 
-    // indexes beyond 255 are used for internal colors
-	"#c0caf5", /* default cursor colour */
-	"#1a1b26", /* default inverted cursor colour */
-	"#c0caf5", /* default foreground colour */
-	"#1a1b26", /* default background colour */
-    "#c0caf5", /* default selection foreground colour */
-    "#33467C", /* default selection background colour */
+	/* more colors can be added after 255 to use with DefaultXX */
+	"#cccccc",
+	"#555555",
+	"gray90", /* default foreground colour */
+	"black", /* default background colour */
 };
 
-unsigned int defaultcs = 256;
-static unsigned int defaultrcs = 257;
-unsigned int defaultfg = 258;
-unsigned int defaultbg = 259;
-unsigned int selectionfg = 260;
-unsigned int selectionbg = 261;
-/* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
-/* Else if 1 keep original foreground-color of each cell => more colors :) */
-static int ignoreselfg = 1;
 
 /*
-+ * 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
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
  */
-static unsigned int cursorstyle = 1;
-static Rune stcursor = 0x2603; /* snowman ("☃") */
+unsigned int defaultfg = 258;
+unsigned int defaultbg = 259;
+unsigned int defaultcs = 256;
+static unsigned int defaultrcs = 257;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static unsigned int cursorshape = 2;
 
 /*
  * Default columns and rows numbers
@@ -199,9 +176,11 @@ static uint forcemousemod = ShiftMask;
  */
 static MouseShortcut mshortcuts[] = {
 	/* mask                 button   function        argument       release */
-	// { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
-	{ ShiftMask,            Button4, kscrollup,      {.i = 3}         }, /* You can increase .i to increase scroll speed*/
-	{ ShiftMask,            Button5, kscrolldown,    {.i = 3}         },
+	{ XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
+	{ ShiftMask,            Button4, ttysend,        {.s = "\033[5;2~"} },
+	{ XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"} },
+	{ ShiftMask,            Button5, ttysend,        {.s = "\033[6;2~"} },
+	{ XK_ANY_MOD,           Button5, ttysend,        {.s = "\005"} },
 };
 
 /* Internal keyboard shortcuts. */
@@ -214,16 +193,14 @@ static Shortcut shortcuts[] = {
 	{ 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 = +5} },
-	{ TERMMOD,              XK_Next,        zoom,           {.f = -5} },
+	{ 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_Y,           selpaste,       {.i =  0} },
 	{ ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
 	{ TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
-	{ ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
-	{ ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
 };
 
 /*
@@ -301,7 +278,7 @@ static Key key[] = {
 	{ 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_KP_Delete,     XK_ANY_MOD,     "\033[3~",      -1,    0},
+	{ XK_KP_Delete,     XK_ANY_MOD,     "\033[P",       -1,    0},
 	{ XK_KP_Delete,     XK_ANY_MOD,     "\033[3~",      +1,    0},
 	{ XK_KP_Multiply,   XK_ANY_MOD,     "\033Oj",       +2,    0},
 	{ XK_KP_Add,        XK_ANY_MOD,     "\033Ok",       +2,    0},
@@ -369,7 +346,7 @@ static Key key[] = {
 	{ XK_Delete,        ControlMask,    "\033[3;5~",    +1,    0},
 	{ XK_Delete,        ShiftMask,      "\033[2K",      -1,    0},
 	{ XK_Delete,        ShiftMask,      "\033[3;2~",    +1,    0},
-	{ XK_Delete,        XK_ANY_MOD,     "\033[3~",      -1,    0},
+	{ XK_Delete,        XK_ANY_MOD,     "\033[P",       -1,    0},
 	{ XK_Delete,        XK_ANY_MOD,     "\033[3~",      +1,    0},
 	{ XK_BackSpace,     XK_NO_MOD,      "\177",          0,    0},
 	{ XK_BackSpace,     Mod1Mask,       "\033\177",      0,    0},
diff --git a/config.mk b/config.mk
index 3e13e53..1e306f8 100644
--- a/config.mk
+++ b/config.mk
@@ -15,12 +15,10 @@ PKG_CONFIG = pkg-config
 # includes and libs
 INCS = -I$(X11INC) \
        `$(PKG_CONFIG) --cflags fontconfig` \
-       `$(PKG_CONFIG) --cflags freetype2` \
-       `$(PKG_CONFIG) --cflags harfbuzz`
+       `$(PKG_CONFIG) --cflags freetype2`
 LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
        `$(PKG_CONFIG) --libs fontconfig` \
-       `$(PKG_CONFIG) --libs freetype2` \
-       `$(PKG_CONFIG) --libs harfbuzz`
+       `$(PKG_CONFIG) --libs freetype2`
 
 # flags
 STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
diff --git a/hb.c b/hb.c
deleted file mode 100644
index b42f357..0000000
--- a/hb.c
+++ /dev/null
@@ -1,124 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <X11/Xft/Xft.h>
-#include <X11/cursorfont.h>
-#include <hb.h>
-#include <hb-ft.h>
-
-#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 }
-#define BUFFER_STEP 256
-
-hb_font_t *hbfindfont(XftFont *match);
-
-typedef struct {
-	XftFont *match;
-	hb_font_t *font;
-} HbFontMatch;
-
-typedef struct {
-	size_t capacity;
-	HbFontMatch *fonts;
-} HbFontCache;
-
-static HbFontCache hbfontcache = { 0, NULL };
-
-typedef struct {
-	size_t capacity;
-	Rune *runes;
-} RuneBuffer;
-
-static RuneBuffer hbrunebuffer = { 0, 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[] = { };
-
-void
-hbunloadfonts()
-{
-	for (int i = 0; i < hbfontcache.capacity; i++) {
-		hb_font_destroy(hbfontcache.fonts[i].font);
-		XftUnlockFace(hbfontcache.fonts[i].match);
-	}
-
-	if (hbfontcache.fonts != NULL) {
-		free(hbfontcache.fonts);
-		hbfontcache.fonts = NULL;
-	}
-	hbfontcache.capacity = 0;
-}
-
-hb_font_t *
-hbfindfont(XftFont *match)
-{
-	for (int i = 0; i < hbfontcache.capacity; i++) {
-		if (hbfontcache.fonts[i].match == match)
-			return hbfontcache.fonts[i].font;
-	}
-
-	/* Font not found in cache, caching it now. */
-	hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 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.fonts[hbfontcache.capacity].match = match;
-	hbfontcache.fonts[hbfontcache.capacity].font = font;
-	hbfontcache.capacity += 1;
-
-	return font;
-}
-
-void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-	ushort mode = USHRT_MAX;
-	unsigned int glyph_count;
-	int rune_idx, glyph_idx, 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);
-
-	/* Resize the buffer if required length is larger. */
-	if (hbrunebuffer.capacity < length) {
-		hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-		hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-	}
-
-	/* Fill buffer with codepoints. */
-	for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-		hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-		mode = glyphs[glyph_idx].mode;
-		if (mode & ATTR_WDUMMY)
-			hbrunebuffer.runes[rune_idx] = 0x0020;
-	}
-	hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-
-	/* 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
deleted file mode 100644
index 88de9bd..0000000
--- a/hb.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <X11/Xft/Xft.h>
-#include <hb.h>
-#include <hb-ft.h>
-
-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/patches/st-scrollback-0.8.5.diff b/patches/st-scrollback-0.8.5.diff
deleted file mode 100644
index 750111d..0000000
--- a/patches/st-scrollback-0.8.5.diff
+++ /dev/null
@@ -1,350 +0,0 @@
-diff --git a/config.def.h b/config.def.h
-index 91ab8ca..e3b469b 100644
---- a/config.def.h
-+++ b/config.def.h
-@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = {
- 	{ TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
- 	{ ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
- 	{ TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
-+	{ ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
-+	{ ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
- };
- 
- /*
-diff --git a/st.c b/st.c
-index 51049ba..cd750f2 100644
---- a/st.c
-+++ b/st.c
-@@ -35,6 +35,7 @@
- #define ESC_ARG_SIZ   16
- #define STR_BUF_SIZ   ESC_BUF_SIZ
- #define STR_ARG_SIZ   ESC_ARG_SIZ
-+#define HISTSIZE      2000
- 
- /* macros */
- #define IS_SET(flag)		((term.mode & (flag)) != 0)
-@@ -42,6 +43,9 @@
- #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
- #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
- #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
-+#define TLINE(y)		((y) < term.scr ? term.hist[((y) + term.histi - \
-+				term.scr + HISTSIZE + 1) % HISTSIZE] : \
-+				term.line[(y) - term.scr])
- 
- enum term_mode {
- 	MODE_WRAP        = 1 << 0,
-@@ -115,6 +119,9 @@ typedef struct {
- 	int col;      /* nb col */
- 	Line *line;   /* screen */
- 	Line *alt;    /* alternate screen */
-+	Line hist[HISTSIZE]; /* history buffer */
-+	int histi;    /* history index */
-+	int scr;      /* scroll back */
- 	int *dirty;   /* dirtyness of lines */
- 	TCursor c;    /* cursor */
- 	int ocx;      /* old cursor col */
-@@ -184,8 +191,8 @@ 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 tscrollup(int, int, int);
-+static void tscrolldown(int, int, int);
- static void tsetattr(const int *, int);
- static void tsetchar(Rune, const Glyph *, int, int);
- static void tsetdirt(int, int);
-@@ -416,10 +423,10 @@ tlinelen(int y)
- {
- 	int i = term.col;
- 
--	if (term.line[y][i - 1].mode & ATTR_WRAP)
-+	if (TLINE(y)[i - 1].mode & ATTR_WRAP)
- 		return i;
- 
--	while (i > 0 && term.line[y][i - 1].u == ' ')
-+	while (i > 0 && TLINE(y)[i - 1].u == ' ')
- 		--i;
- 
- 	return i;
-@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction)
- 		 * Snap around if the word wraps around at the end or
- 		 * beginning of a line.
- 		 */
--		prevgp = &term.line[*y][*x];
-+		prevgp = &TLINE(*y)[*x];
- 		prevdelim = ISDELIM(prevgp->u);
- 		for (;;) {
- 			newx = *x + direction;
-@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction)
- 					yt = *y, xt = *x;
- 				else
- 					yt = newy, xt = newx;
--				if (!(term.line[yt][xt].mode & ATTR_WRAP))
-+				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
- 					break;
- 			}
- 
- 			if (newx >= tlinelen(newy))
- 				break;
- 
--			gp = &term.line[newy][newx];
-+			gp = &TLINE(newy)[newx];
- 			delim = ISDELIM(gp->u);
- 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
- 					|| (delim && gp->u != prevgp->u)))
-@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction)
- 		*x = (direction < 0) ? 0 : term.col - 1;
- 		if (direction < 0) {
- 			for (; *y > 0; *y += direction) {
--				if (!(term.line[*y-1][term.col-1].mode
-+				if (!(TLINE(*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
-+				if (!(TLINE(*y)[term.col-1].mode
- 						& ATTR_WRAP)) {
- 					break;
- 				}
-@@ -609,13 +616,13 @@ getsel(void)
- 		}
- 
- 		if (sel.type == SEL_RECTANGULAR) {
--			gp = &term.line[y][sel.nb.x];
-+			gp = &TLINE(y)[sel.nb.x];
- 			lastx = sel.ne.x;
- 		} else {
--			gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
-+			gp = &TLINE(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)];
-+		last = &TLINE(y)[MIN(lastx, linelen-1)];
- 		while (last >= gp && last->u == ' ')
- 			--last;
- 
-@@ -851,6 +858,9 @@ void
- ttywrite(const char *s, size_t n, int may_echo)
- {
- 	const char *next;
-+	Arg arg = (Arg) { .i = term.scr };
-+
-+	kscrolldown(&arg);
- 
- 	if (may_echo && IS_SET(MODE_ECHO))
- 		twrite(s, n, 1);
-@@ -1062,12 +1072,52 @@ tswapscreen(void)
- }
- 
- void
--tscrolldown(int orig, int n)
-+kscrolldown(const Arg* a)
-+{
-+	int n = a->i;
-+
-+	if (n < 0)
-+		n = term.row + n;
-+
-+	if (n > term.scr)
-+		n = term.scr;
-+
-+	if (term.scr > 0) {
-+		term.scr -= n;
-+		selscroll(0, -n);
-+		tfulldirt();
-+	}
-+}
-+
-+void
-+kscrollup(const Arg* a)
-+{
-+	int n = a->i;
-+
-+	if (n < 0)
-+		n = term.row + n;
-+
-+	if (term.scr <= HISTSIZE-n) {
-+		term.scr += n;
-+		selscroll(0, n);
-+		tfulldirt();
-+	}
-+}
-+
-+void
-+tscrolldown(int orig, int n, int copyhist)
- {
- 	int i;
- 	Line temp;
- 
- 	LIMIT(n, 0, term.bot-orig+1);
-+	if (copyhist) {
-+		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
-+		temp = term.hist[term.histi];
-+		term.hist[term.histi] = term.line[term.bot];
-+		term.line[term.bot] = temp;
-+	}
-+
- 
- 	tsetdirt(orig, term.bot-n);
- 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
-@@ -1078,17 +1128,28 @@ tscrolldown(int orig, int n)
- 		term.line[i-n] = temp;
- 	}
- 
--	selscroll(orig, n);
-+	if (term.scr == 0)
-+		selscroll(orig, n);
- }
- 
- void
--tscrollup(int orig, int n)
-+tscrollup(int orig, int n, int copyhist)
- {
- 	int i;
- 	Line temp;
- 
- 	LIMIT(n, 0, term.bot-orig+1);
- 
-+	if (copyhist) {
-+		term.histi = (term.histi + 1) % HISTSIZE;
-+		temp = term.hist[term.histi];
-+		term.hist[term.histi] = term.line[orig];
-+		term.line[orig] = temp;
-+	}
-+
-+	if (term.scr > 0 && term.scr < HISTSIZE)
-+		term.scr = MIN(term.scr + n, HISTSIZE-1);
-+
- 	tclearregion(0, orig, term.col-1, orig+n-1);
- 	tsetdirt(orig+n, term.bot);
- 
-@@ -1098,7 +1159,8 @@ tscrollup(int orig, int n)
- 		term.line[i+n] = temp;
- 	}
- 
--	selscroll(orig, -n);
-+	if (term.scr == 0)
-+		selscroll(orig, -n);
- }
- 
- void
-@@ -1127,7 +1189,7 @@ tnewline(int first_col)
- 	int y = term.c.y;
- 
- 	if (y == term.bot) {
--		tscrollup(term.top, 1);
-+		tscrollup(term.top, 1, 1);
- 	} else {
- 		y++;
- 	}
-@@ -1292,14 +1354,14 @@ void
- tinsertblankline(int n)
- {
- 	if (BETWEEN(term.c.y, term.top, term.bot))
--		tscrolldown(term.c.y, n);
-+		tscrolldown(term.c.y, n, 0);
- }
- 
- void
- tdeleteline(int n)
- {
- 	if (BETWEEN(term.c.y, term.top, term.bot))
--		tscrollup(term.c.y, n);
-+		tscrollup(term.c.y, n, 0);
- }
- 
- int32_t
-@@ -1736,11 +1798,11 @@ csihandle(void)
- 		break;
- 	case 'S': /* SU -- Scroll <n> line up */
- 		DEFAULT(csiescseq.arg[0], 1);
--		tscrollup(term.top, csiescseq.arg[0]);
-+		tscrollup(term.top, csiescseq.arg[0], 0);
- 		break;
- 	case 'T': /* SD -- Scroll <n> line down */
- 		DEFAULT(csiescseq.arg[0], 1);
--		tscrolldown(term.top, csiescseq.arg[0]);
-+		tscrolldown(term.top, csiescseq.arg[0], 0);
- 		break;
- 	case 'L': /* IL -- Insert <n> blank lines */
- 		DEFAULT(csiescseq.arg[0], 1);
-@@ -2330,7 +2392,7 @@ eschandle(uchar ascii)
- 		return 0;
- 	case 'D': /* IND -- Linefeed */
- 		if (term.c.y == term.bot) {
--			tscrollup(term.top, 1);
-+			tscrollup(term.top, 1, 1);
- 		} else {
- 			tmoveto(term.c.x, term.c.y+1);
- 		}
-@@ -2343,7 +2405,7 @@ eschandle(uchar ascii)
- 		break;
- 	case 'M': /* RI -- Reverse index */
- 		if (term.c.y == term.top) {
--			tscrolldown(term.top, 1);
-+			tscrolldown(term.top, 1, 1);
- 		} else {
- 			tmoveto(term.c.x, term.c.y-1);
- 		}
-@@ -2557,7 +2619,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
- void
- tresize(int col, int row)
- {
--	int i;
-+	int i, j;
- 	int minrow = MIN(row, term.row);
- 	int mincol = MIN(col, term.col);
- 	int *bp;
-@@ -2594,6 +2656,14 @@ tresize(int col, int row)
- 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
- 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
- 
-+	for (i = 0; i < HISTSIZE; i++) {
-+		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
-+		for (j = mincol; j < col; j++) {
-+			term.hist[i][j] = term.c.attr;
-+			term.hist[i][j].u = ' ';
-+		}
-+	}
-+
- 	/* 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));
-@@ -2652,7 +2722,7 @@ drawregion(int x1, int y1, int x2, int y2)
- 			continue;
- 
- 		term.dirty[y] = 0;
--		xdrawline(term.line[y], x1, y, x2);
-+		xdrawline(TLINE(y), x1, y, x2);
- 	}
- }
- 
-@@ -2673,8 +2743,9 @@ draw(void)
- 		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]);
-+	if (term.scr == 0)
-+		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-+				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
- 	term.ocx = cx;
- 	term.ocy = term.c.y;
- 	xfinishdraw();
-diff --git a/st.h b/st.h
-index 519b9bd..da36b34 100644
---- a/st.h
-+++ b/st.h
-@@ -81,6 +81,8 @@ void die(const char *, ...);
- void redraw(void);
- void draw(void);
- 
-+void kscrolldown(const Arg *);
-+void kscrollup(const Arg *);
- void printscreen(const Arg *);
- void printsel(const Arg *);
- void sendbreak(const Arg *);
diff --git a/st.1 b/st.1
index b82beb3..39120b4 100644
--- a/st.1
+++ b/st.1
@@ -6,8 +6,6 @@ st \- simple terminal
 .RB [ \-aiv ]
 .RB [ \-c
 .IR class ]
-.RB [ \-d
-.IR path ]
 .RB [ \-f
 .IR font ]
 .RB [ \-g
@@ -32,8 +30,6 @@ st \- simple terminal
 .RB [ \-aiv ]
 .RB [ \-c
 .IR class ]
-.RB [ \-d
-.IR path ]
 .RB [ \-f
 .IR font ]
 .RB [ \-g
@@ -62,10 +58,6 @@ disable alternate screens in terminal
 .BI \-c " class"
 defines the window class (default $TERM).
 .TP
-.BI \-d " path"
-changes the working directory to
-.IR path .
-.TP
 .BI \-f " font"
 defines the
 .I font
diff --git a/st.c b/st.c
index e98d71f..d6478f5 100644
--- a/st.c
+++ b/st.c
@@ -33,10 +33,8 @@
 #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
-#define HISTSIZE      5000
 
 /* macros */
 #define IS_SET(flag)		((term.mode & (flag)) != 0)
@@ -44,9 +42,6 @@
 #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
 #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
-#define TLINE(y)		((y) < term.scr ? term.hist[((y) + term.histi - \
-				term.scr + HISTSIZE + 1) % HISTSIZE] : \
-				term.line[(y) - term.scr])
 
 enum term_mode {
 	MODE_WRAP        = 1 << 0,
@@ -120,9 +115,6 @@ typedef struct {
 	int col;      /* nb col */
 	Line *line;   /* screen */
 	Line *alt;    /* alternate screen */
-	Line hist[HISTSIZE]; /* history buffer */
-	int histi;    /* history index */
-	int scr;      /* scroll back */
 	int *dirty;   /* dirtyness of lines */
 	TCursor c;    /* cursor */
 	int ocx;      /* old cursor col */
@@ -147,7 +139,6 @@ typedef struct {
 	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 */
@@ -168,7 +159,6 @@ 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 osc_color_response(int, int, int);
@@ -195,8 +185,8 @@ static void tnewline(int);
 static void tputtab(int);
 static void tputc(Rune);
 static void treset(void);
-static void tscrollup(int, int, int);
-static void tscrolldown(int, int, int);
+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);
@@ -419,10 +409,10 @@ tlinelen(int y)
 {
 	int i = term.col;
 
-	if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+	if (term.line[y][i - 1].mode & ATTR_WRAP)
 		return i;
 
-	while (i > 0 && TLINE(y)[i - 1].u == ' ')
+	while (i > 0 && term.line[y][i - 1].u == ' ')
 		--i;
 
 	return i;
@@ -531,7 +521,7 @@ selsnap(int *x, int *y, int direction)
 		 * Snap around if the word wraps around at the end or
 		 * beginning of a line.
 		 */
-		prevgp = &TLINE(*y)[*x];
+		prevgp = &term.line[*y][*x];
 		prevdelim = ISDELIM(prevgp->u);
 		for (;;) {
 			newx = *x + direction;
@@ -546,14 +536,14 @@ selsnap(int *x, int *y, int direction)
 					yt = *y, xt = *x;
 				else
 					yt = newy, xt = newx;
-				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
+				if (!(term.line[yt][xt].mode & ATTR_WRAP))
 					break;
 			}
 
 			if (newx >= tlinelen(newy))
 				break;
 
-			gp = &TLINE(newy)[newx];
+			gp = &term.line[newy][newx];
 			delim = ISDELIM(gp->u);
 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
 					|| (delim && gp->u != prevgp->u)))
@@ -574,14 +564,14 @@ selsnap(int *x, int *y, int direction)
 		*x = (direction < 0) ? 0 : term.col - 1;
 		if (direction < 0) {
 			for (; *y > 0; *y += direction) {
-				if (!(TLINE(*y-1)[term.col-1].mode
+				if (!(term.line[*y-1][term.col-1].mode
 						& ATTR_WRAP)) {
 					break;
 				}
 			}
 		} else if (direction > 0) {
 			for (; *y < term.row-1; *y += direction) {
-				if (!(TLINE(*y)[term.col-1].mode
+				if (!(term.line[*y][term.col-1].mode
 						& ATTR_WRAP)) {
 					break;
 				}
@@ -612,13 +602,13 @@ getsel(void)
 		}
 
 		if (sel.type == SEL_RECTANGULAR) {
-			gp = &TLINE(y)[sel.nb.x];
+			gp = &term.line[y][sel.nb.x];
 			lastx = sel.ne.x;
 		} else {
-			gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
+			gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 		}
-		last = &TLINE(y)[MIN(lastx, linelen-1)];
+		last = &term.line[y][MIN(lastx, linelen-1)];
 		while (last >= gp && last->u == ' ')
 			--last;
 
@@ -854,9 +844,6 @@ void
 ttywrite(const char *s, size_t n, int may_echo)
 {
 	const char *next;
-	Arg arg = (Arg) { .i = term.scr };
-
-	kscrolldown(&arg);
 
 	if (may_echo && IS_SET(MODE_ECHO))
 		twrite(s, n, 1);
@@ -1056,12 +1043,6 @@ tnew(int col, int row)
 	treset();
 }
 
-int
-tisaltscr(void)
-{
-	return IS_SET(MODE_ALTSCREEN);
-}
-
 void
 tswapscreen(void)
 {
@@ -1074,52 +1055,12 @@ tswapscreen(void)
 }
 
 void
-kscrolldown(const Arg* a)
-{
-	int n = a->i;
-
-	if (n < 0)
-		n = term.row + n;
-
-	if (n > term.scr)
-		n = term.scr;
-
-	if (term.scr > 0) {
-		term.scr -= n;
-		selscroll(0, -n);
-		tfulldirt();
-	}
-}
-
-void
-kscrollup(const Arg* a)
-{
-	int n = a->i;
-
-	if (n < 0)
-		n = term.row + n;
-
-	if (term.scr <= HISTSIZE-n) {
-		term.scr += n;
-		selscroll(0, n);
-		tfulldirt();
-	}
-}
-
-void
-tscrolldown(int orig, int n, int copyhist)
+tscrolldown(int orig, int n)
 {
 	int i;
 	Line temp;
 
 	LIMIT(n, 0, term.bot-orig+1);
-	if (copyhist) {
-		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
-		temp = term.hist[term.histi];
-		term.hist[term.histi] = term.line[term.bot];
-		term.line[term.bot] = temp;
-	}
-
 
 	tsetdirt(orig, term.bot-n);
 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
@@ -1130,28 +1071,17 @@ tscrolldown(int orig, int n, int copyhist)
 		term.line[i-n] = temp;
 	}
 
-	if (term.scr == 0)
-		selscroll(orig, n);
+	selscroll(orig, n);
 }
 
 void
-tscrollup(int orig, int n, int copyhist)
+tscrollup(int orig, int n)
 {
 	int i;
 	Line temp;
 
 	LIMIT(n, 0, term.bot-orig+1);
 
-	if (copyhist) {
-		term.histi = (term.histi + 1) % HISTSIZE;
-		temp = term.hist[term.histi];
-		term.hist[term.histi] = term.line[orig];
-		term.line[orig] = temp;
-	}
-
-	if (term.scr > 0 && term.scr < HISTSIZE)
-		term.scr = MIN(term.scr + n, HISTSIZE-1);
-
 	tclearregion(0, orig, term.col-1, orig+n-1);
 	tsetdirt(orig+n, term.bot);
 
@@ -1161,14 +1091,13 @@ tscrollup(int orig, int n, int copyhist)
 		term.line[i+n] = temp;
 	}
 
-	if (term.scr == 0)
-		selscroll(orig, -n);
+	selscroll(orig, -n);
 }
 
 void
 selscroll(int orig, int n)
 {
-	if (sel.ob.x == -1)
+	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)) {
@@ -1191,35 +1120,13 @@ tnewline(int first_col)
 	int y = term.c.y;
 
 	if (y == term.bot) {
-		tscrollup(term.top, 1, 1);
+		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)
 {
@@ -1242,7 +1149,6 @@ csiparse(void)
 			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++;
@@ -1309,9 +1215,6 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 	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
@@ -1382,14 +1285,14 @@ void
 tinsertblankline(int n)
 {
 	if (BETWEEN(term.c.y, term.top, term.bot))
-		tscrolldown(term.c.y, n, 0);
+		tscrolldown(term.c.y, n);
 }
 
 void
 tdeleteline(int n)
 {
 	if (BETWEEN(term.c.y, term.top, term.bot))
-		tscrollup(term.c.y, n, 0);
+		tscrollup(term.c.y, n);
 }
 
 int32_t
@@ -1462,10 +1365,6 @@ tsetattr(const int *attr, int l)
 				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;
@@ -1477,14 +1376,7 @@ tsetattr(const int *attr, int l)
 			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;
+			term.c.attr.mode |= ATTR_UNDERLINE;
 			break;
 		case 5: /* slow blink */
 			/* FALLTHROUGH */
@@ -1535,18 +1427,6 @@ tsetattr(const int *attr, int l)
 		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;
@@ -1849,11 +1729,11 @@ csihandle(void)
 		break;
 	case 'S': /* SU -- Scroll <n> line up */
 		DEFAULT(csiescseq.arg[0], 1);
-		tscrollup(term.top, csiescseq.arg[0], 0);
+		tscrollup(term.top, csiescseq.arg[0]);
 		break;
 	case 'T': /* SD -- Scroll <n> line down */
 		DEFAULT(csiescseq.arg[0], 1);
-		tscrolldown(term.top, csiescseq.arg[0], 0);
+		tscrolldown(term.top, csiescseq.arg[0]);
 		break;
 	case 'L': /* IL -- Insert <n> blank lines */
 		DEFAULT(csiescseq.arg[0], 1);
@@ -1929,33 +1809,6 @@ csihandle(void)
 			goto unknown;
 		}
 		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;
-		}
 	}
 }
 
@@ -2034,7 +1887,7 @@ strhandle(void)
 		switch (par) {
 		case 0:
 			if (narg > 1) {
-				xsettitle(strescseq.args[1], 0);
+				xsettitle(strescseq.args[1]);
 				xseticontitle(strescseq.args[1]);
 			}
 			return;
@@ -2044,7 +1897,7 @@ strhandle(void)
 			return;
 		case 2:
 			if (narg > 1)
-				xsettitle(strescseq.args[1], 0);
+				xsettitle(strescseq.args[1]);
 			return;
 		case 52:
 			if (narg > 2 && allowwindowops) {
@@ -2103,7 +1956,7 @@ strhandle(void)
 		}
 		break;
 	case 'k': /* old title set compatibility */
-		xsettitle(strescseq.args[0], 0);
+		xsettitle(strescseq.args[0]);
 		return;
 	case 'P': /* DCS -- Device Control String */
 	case '_': /* APC -- Application Program Command */
@@ -2452,7 +2305,7 @@ eschandle(uchar ascii)
 		return 0;
 	case 'D': /* IND -- Linefeed */
 		if (term.c.y == term.bot) {
-			tscrollup(term.top, 1, 1);
+			tscrollup(term.top, 1);
 		} else {
 			tmoveto(term.c.x, term.c.y+1);
 		}
@@ -2465,7 +2318,7 @@ eschandle(uchar ascii)
 		break;
 	case 'M': /* RI -- Reverse index */
 		if (term.c.y == term.top) {
-			tscrolldown(term.top, 1, 1);
+			tscrolldown(term.top, 1);
 		} else {
 			tmoveto(term.c.x, term.c.y-1);
 		}
@@ -2475,9 +2328,9 @@ eschandle(uchar ascii)
 		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);
@@ -2619,11 +2472,16 @@ check_control_code:
 		gp = &term.line[term.c.y][term.c.x];
 	}
 
-	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
+	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) {
-		tnewline(1);
+		if (IS_SET(MODE_WRAP))
+			tnewline(1);
+		else
+			tmoveto(term.col - width, term.c.y);
 		gp = &term.line[term.c.y][term.c.x];
 	}
 
@@ -2683,7 +2541,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
 void
 tresize(int col, int row)
 {
-	int i, j;
+	int i;
 	int minrow = MIN(row, term.row);
 	int mincol = MIN(col, term.col);
 	int *bp;
@@ -2720,14 +2578,6 @@ tresize(int col, int row)
 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
 
-	for (i = 0; i < HISTSIZE; i++) {
-		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
-		for (j = mincol; j < col; j++) {
-			term.hist[i][j] = term.c.attr;
-			term.hist[i][j].u = ' ';
-		}
-	}
-
 	/* 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));
@@ -2773,7 +2623,7 @@ tresize(int col, int row)
 void
 resettitle(void)
 {
-	xsettitle(NULL, 0);
+	xsettitle(NULL);
 }
 
 void
@@ -2786,7 +2636,7 @@ drawregion(int x1, int y1, int x2, int y2)
 			continue;
 
 		term.dirty[y] = 0;
-		xdrawline(TLINE(y), x1, y, x2);
+		xdrawline(term.line[y], x1, y, x2);
 	}
 }
 
@@ -2807,10 +2657,8 @@ draw(void)
 		cx--;
 
 	drawregion(0, 0, term.col, term.row);
-    if (term.scr == 0)
-        xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
-                term.ocx, term.ocy, TLINE(term.ocy)[term.ocx],
-                TLINE(term.ocy), term.col);
+	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
 	term.ocx = cx;
 	term.ocy = term.c.y;
 	xfinishdraw();
diff --git a/st.h b/st.h
index f0995fb..fd3b0d8 100644
--- a/st.h
+++ b/st.h
@@ -11,8 +11,7 @@
 #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)) != ((b).mode & (~ATTR_WRAP)) || \
-				(a).fg != (b).fg || \
+#define ATTRCMP(a, b)		((a).mode != (b).mode || (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)
@@ -34,10 +33,7 @@ enum glyph_attribute {
 	ATTR_WRAP       = 1 << 8,
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
-	ATTR_BOXDRAW    = 1 << 11,
-	ATTR_SELECTED   = 1 << 12,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
-	ATTR_DIRTYUNDERLINE = 1 << 15,
 };
 
 enum selection_mode {
@@ -69,8 +65,6 @@ typedef struct {
 	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;
@@ -87,15 +81,12 @@ void die(const char *, ...);
 void redraw(void);
 void draw(void);
 
-void kscrolldown(const Arg *);
-void kscrollup(const Arg *);
 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);
@@ -120,14 +111,6 @@ void *xmalloc(size_t);
 void *xrealloc(void *, size_t);
 char *xstrdup(const char *);
 
-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
-
 /* config.h globals */
 extern char *utmp;
 extern char *scroll;
@@ -141,4 +124,3 @@ extern unsigned int tabspaces;
 extern unsigned int defaultfg;
 extern unsigned int defaultbg;
 extern unsigned int defaultcs;
-extern const int boxdraw, boxdraw_bold, boxdraw_braille;
diff --git a/st.info b/st.info
index c3bfbb1..efab2cf 100644
--- a/st.info
+++ b/st.info
@@ -1,5 +1,4 @@
 st-mono| simpleterm monocolor,
-	Su,
 	acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
 	am,
 	bce,
@@ -162,7 +161,7 @@ st-mono| simpleterm monocolor,
 	rin=\E[%p1%dT,
 	ritm=\E[23m,
 	rmacs=\E(B,
-	rmcup=\E[?1049l\E[23;0;0t,
+	rmcup=\E[?1049l,
 	rmir=\E[4l,
 	rmkx=\E[?1l\E>,
 	rmso=\E[27m,
@@ -173,7 +172,7 @@ st-mono| simpleterm monocolor,
 	sitm=\E[3m,
 	sgr0=\E[0m,
 	smacs=\E(0,
-	smcup=\E[?1049h\E[22;0;0t,
+	smcup=\E[?1049h,
 	smir=\E[4h,
 	smkx=\E[?1h\E=,
 	smso=\E[7m,
@@ -185,6 +184,10 @@ st-mono| simpleterm monocolor,
 # 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)
diff --git a/win.h b/win.h
index 8712eea..6de960d 100644
--- a/win.h
+++ b/win.h
@@ -25,16 +25,14 @@ enum win_mode {
 
 void xbell(void);
 void xclipcopy(void);
-void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
+void xdrawcursor(int, int, Glyph, int, int, Glyph);
 void xdrawline(Line, int, int, int);
 void xfinishdraw(void);
 void xloadcols(void);
 int xsetcolorname(int, const char *);
 int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
 void xseticontitle(char *);
-void xfreetitlestack(void);
-void xsettitle(char *, int);
-void xpushtitle(void);
+void xsettitle(char *);
 int xsetcursor(int);
 void xsetmode(int, unsigned int);
 void xsetpointermotion(int);
diff --git a/x.c b/x.c
index 886205f..b36fb8c 100644
--- a/x.c
+++ b/x.c
@@ -19,7 +19,6 @@ char *argv0;
 #include "arg.h"
 #include "st.h"
 #include "win.h"
-#include "hb.h"
 
 /* types used in config.h */
 typedef struct {
@@ -35,7 +34,6 @@ typedef struct {
 	void (*func)(const Arg *);
 	const Arg arg;
 	uint  release;
-    int  altscrn;  /* 0: don't care, -1: not alt screen, 1: alt screen */
 } MouseShortcut;
 
 typedef struct {
@@ -47,14 +45,6 @@ typedef struct {
 	signed char appcursor; /* application cursor */
 } Key;
 
-/* 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
@@ -73,9 +63,6 @@ static void ttysend(const Arg *);
 /* 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
@@ -94,7 +81,6 @@ typedef XftGlyphFontSpec GlyphFontSpec;
 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 */
@@ -155,7 +141,6 @@ typedef struct {
 } DC;
 
 static inline ushort sixd_to_16bit(int);
-static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
 static void xdrawglyph(Glyph, int, int);
@@ -235,8 +220,6 @@ static DC dc;
 static XWindow xw;
 static XSelection xsel;
 static TermWindow win;
-static int tstki; /* title stack index */
-static char *titlestack[TITLESTACKSIZE]; /* title stack */
 
 /* Font Ring Cache */
 enum {
@@ -268,10 +251,8 @@ 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 uint buttons; /* bit field of pressed buttons */
-static int cursorblinks = 0;
 
 void
 clipcopy(const Arg *dummy)
@@ -350,7 +331,7 @@ ttysend(const Arg *arg)
 int
 evcol(XEvent *e)
 {
-	int x = e->xbutton.x - win.hborderpx;
+	int x = e->xbutton.x - borderpx;
 	LIMIT(x, 0, win.tw - 1);
 	return x / win.cw;
 }
@@ -358,7 +339,7 @@ evcol(XEvent *e)
 int
 evrow(XEvent *e)
 {
-	int y = e->xbutton.y - win.vborderpx;
+	int y = e->xbutton.y - borderpx;
 	LIMIT(y, 0, win.th - 1);
 	return y / win.ch;
 }
@@ -474,7 +455,6 @@ mouseaction(XEvent *e, uint release)
 	for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
 		if (ms->release == release &&
 		    ms->button == e->xbutton.button &&
-            (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) &&
 		    (match(ms->mod, state) ||  /* exact or forced */
 		     match(ms->mod, state & ~forcemousemod))) {
 			ms->func(&(ms->arg));
@@ -759,9 +739,6 @@ cresize(int width, int height)
 	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);
@@ -780,7 +757,7 @@ xresize(int col, int row)
 	xclear(0, 0, win.w, win.h);
 
 	/* resize to new width */
-	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
+	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
 }
 
 ushort
@@ -841,7 +818,7 @@ xloadcols(void)
 int
 xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
 {
-	if (!BETWEEN(x, 0, dc.collen))
+	if (!BETWEEN(x, 0, dc.collen - 1))
 		return 1;
 
 	*r = dc.col[x].color.red >> 8;
@@ -856,7 +833,7 @@ xsetcolorname(int x, const char *name)
 {
 	Color ncolor;
 
-	if (!BETWEEN(x, 0, dc.collen))
+	if (!BETWEEN(x, 0, dc.collen - 1))
 		return 1;
 
 	if (!xloadcolor(x, name, &ncolor))
@@ -892,8 +869,8 @@ xhints(void)
 	sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
 	sizeh->height = win.h;
 	sizeh->width = win.w;
-	sizeh->height_inc = 1;
-	sizeh->width_inc = 1;
+	sizeh->height_inc = win.ch;
+	sizeh->width_inc = win.cw;
 	sizeh->base_height = 2 * borderpx;
 	sizeh->base_width = 2 * borderpx;
 	sizeh->min_height = win.ch + 2 * borderpx;
@@ -1085,9 +1062,6 @@ xunloadfont(Font *f)
 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);
@@ -1178,8 +1152,8 @@ xinit(int cols, int rows)
 	xloadcols();
 
 	/* adjust fixed window geometry */
-	win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
-	win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
+	win.w = 2 * borderpx + cols * win.cw;
+	win.h = 2 * borderpx + rows * win.ch;
 	if (xw.gm & XNegative)
 		xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
 	if (xw.gm & YNegative)
@@ -1211,7 +1185,7 @@ xinit(int cols, int rows)
 	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
 
 	/* font spec buffer */
-	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
+	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
 
 	/* Xft rendering context */
 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@@ -1263,30 +1237,12 @@ xinit(int cols, int rows)
 	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;
+	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
 	ushort mode, prevmode = USHRT_MAX;
 	Font *font = &dc.font;
 	int frcflags = FRC_NORMAL;
@@ -1297,211 +1253,129 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 	FcPattern *fcpattern, *fontpattern;
 	FcFontSet *fcsets[] = { NULL };
 	FcCharSet *fccharset;
-	int i, f, length = 0, start = 0, numspecs = 0;
-	float cluster_xp = xp, cluster_yp = yp;
-	HbTransformData shaped = { 0 };
-
-	/* Initial values. */
-	mode = prevmode = glyphs[0].mode;
-	xresetfontsettings(mode, &font, &frcflags);
+	int i, f, numspecs = 0;
 
 	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+		/* Fetch rune and mode for current glyph. */
+		rune = glyphs[i].u;
 		mode = glyphs[i].mode;
 
 		/* Skip dummy wide-character spacing. */
-		if (mode & ATTR_WDUMMY && i < (len - 1))
+		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);
+		/* Determine font for glyph if different from previous glyph. */
+		if (prevmode != mode) {
+			prevmode = mode;
+			font = &dc.font;
+			frcflags = FRC_NORMAL;
+			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
+			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;
 			}
+			yp = winy + font->ascent;
+		}
 
-			/* Shape the segment. */
-			hbtransform(&shaped, font->match, glyphs, start, length);
-			runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-			cluster_xp = xp; cluster_yp = yp;
-			for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-				int idx = shaped.glyphs[code_idx].cluster;
+		/* Lookup character index with default font. */
+		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+		if (glyphidx) {
+			specs[numspecs].font = font->match;
+			specs[numspecs].glyph = glyphidx;
+			specs[numspecs].x = (short)xp;
+			specs[numspecs].y = (short)yp;
+			xp += runewidth;
+			numspecs++;
+			continue;
+		}
 
-				if (glyphs[start + idx].mode & ATTR_WDUMMY)
-					continue;
-
-				/* Advance the drawing cursor if we've moved to a new cluster */
-				if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-					xp += runewidth;
-					cluster_xp = xp;
-					cluster_yp = yp;
-					runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-				}
-
-				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;
-					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 = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-					specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-					cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-					cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-					numspecs++;
-				} else {
-					/* If it's not found, try to fetch it through the font cache. */
-					rune = glyphs[start + idx].u;
-					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;
-					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;
+		/* Fallback on font cache, search the font cache for match. */
+		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++;
 	}
 
-	hbcleanup(&shaped);
 	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 charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
-	int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
+	int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
 	    width = charlen * win.cw;
 	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
 	XRenderColor colfg, colbg;
@@ -1538,6 +1412,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 		bg = &dc.col[base.bg];
 	}
 
+	/* Change basic system colors [0-7] to bright system colors [8-15] */
+	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
+		fg = &dc.col[base.fg + 8];
+
 	if (IS_SET(MODE_REVERSE)) {
 		if (fg == &dc.col[defaultfg]) {
 			fg = &dc.col[defaultbg];
@@ -1579,12 +1457,6 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 		bg = temp;
 	}
 
-	if (base.mode & ATTR_SELECTED) {
-		bg = &dc.col[selectionbg];
-		if (!ignoreselfg)
-			fg = &dc.col[selectionfg];
-	}
-
 	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
 		fg = bg;
 
@@ -1593,17 +1465,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
 	/* Intelligent cleaning up of the borders. */
 	if (x == 0) {
-		xclear(0, (y == 0)? 0 : winy, win.hborderpx,
+		xclear(0, (y == 0)? 0 : winy, borderpx,
 			winy + win.ch +
-			((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
+			((winy + win.ch >= borderpx + win.th)? win.h : 0));
 	}
-	if (winx + width >= win.hborderpx + win.tw) {
+	if (winx + width >= borderpx + win.tw) {
 		xclear(winx + width, (y == 0)? 0 : winy, win.w,
-			((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
+			((winy + win.ch >= borderpx + 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, 0, winx + width, borderpx);
+	if (winy + win.ch >= borderpx + win.th)
 		xclear(winx, winy + win.ch, winx + width, win.h);
 
 	/* Clean up the region we want to draw to. */
@@ -1616,367 +1488,14 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	r.width = width;
 	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
 
-	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 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) {
-			//XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, width, 1);
-			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);
-    }
+		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
+				width, 1);
+	}
 
 	if (base.mode & ATTR_STRUCK) {
 		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
@@ -1998,17 +1517,14 @@ xdrawglyph(Glyph g, int x, int y)
 }
 
 void
-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
 {
 	Color drawcol;
 
 	/* remove the old cursor */
 	if (selected(ox, oy))
-		og.mode |= ATTR_SELECTED;
-
-	/* Redraw the line where cursor was previously.
-	 * It will restore the ligatures broken by the cursor. */
-	xdrawline(line, 0, oy, len);
+		og.mode ^= ATTR_REVERSE;
+	xdrawglyph(og, ox, oy);
 
 	if (IS_SET(MODE_HIDE))
 		return;
@@ -2016,77 +1532,72 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le
 	/*
 	 * Select the right color for the right mode.
 	 */
-	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
+	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
 
 	if (IS_SET(MODE_REVERSE)) {
 		g.mode |= ATTR_REVERSE;
-		g.fg = defaultcs;
 		g.bg = defaultfg;
-		drawcol = dc.col[defaultrcs];
+		if (selected(cx, cy)) {
+			drawcol = dc.col[defaultcs];
+			g.fg = defaultrcs;
+		} else {
+			drawcol = dc.col[defaultrcs];
+			g.fg = defaultcs;
+		}
 	} else {
-		g.fg = defaultbg;
-		g.bg = defaultcs;
-		drawcol = dc.col[defaultcs];
+		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) {
-		default:
-		case 0: /* blinking block */
-		case 1: /* blinking block (default) */
-			if (IS_SET(MODE_BLINK))
-				break;
+		case 7: /* st extension */
+			g.u = 0x2603; /* snowman (U+2603) */
 			/* FALLTHROUGH */
-		case 2: /* steady block */
+		case 0: /* Blinking Block */
+		case 1: /* Blinking Block (Default) */
+		case 2: /* Steady Block */
 			xdrawglyph(g, cx, cy);
 			break;
-		case 3: /* blinking underline */
-			if (IS_SET(MODE_BLINK))
-				break;
-			/* FALLTHROUGH */
-		case 4: /* steady underline */
+		case 3: /* Blinking Underline */
+		case 4: /* Steady Underline */
 			XftDrawRect(xw.draw, &drawcol,
-					win.hborderpx + cx * win.cw,
-					win.vborderpx + (cy + 1) * win.ch - \
+					borderpx + cx * win.cw,
+					borderpx + (cy + 1) * win.ch - \
 						cursorthickness,
 					win.cw, cursorthickness);
 			break;
-		case 5: /* blinking bar */
-			if (IS_SET(MODE_BLINK))
-				break;
-			/* FALLTHROUGH */
-		case 6: /* steady bar */
+		case 5: /* Blinking bar */
+		case 6: /* Steady bar */
 			XftDrawRect(xw.draw, &drawcol,
-					win.hborderpx + cx * win.cw,
-					win.vborderpx + cy * win.ch,
+					borderpx + cx * win.cw,
+					borderpx + 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,
+				borderpx + cx * win.cw,
+				borderpx + cy * win.ch,
 				win.cw - 1, 1);
 		XftDrawRect(xw.draw, &drawcol,
-				win.hborderpx + cx * win.cw,
-				win.vborderpx + cy * win.ch,
+				borderpx + cx * win.cw,
+				borderpx + cy * win.ch,
 				1, win.ch - 1);
 		XftDrawRect(xw.draw, &drawcol,
-				win.hborderpx + (cx + 1) * win.cw - 1,
-				win.vborderpx + cy * win.ch,
+				borderpx + (cx + 1) * win.cw - 1,
+				borderpx + cy * win.ch,
 				1, win.ch - 1);
 		XftDrawRect(xw.draw, &drawcol,
-				win.hborderpx + cx * win.cw,
-				win.vborderpx + (cy + 1) * win.ch - 1,
+				borderpx + cx * win.cw,
+				borderpx + (cy + 1) * win.ch - 1,
 				win.cw, 1);
 	}
 }
@@ -2115,30 +1626,10 @@ xseticontitle(char *p)
 }
 
 void
-xfreetitlestack(void)
+xsettitle(char *p)
 {
-	for (int i = 0; i < LEN(titlestack); i++) {
-		free(titlestack[i]);
-		titlestack[i] = NULL;
-	}
-}
-
-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;
-	}
+	XTextProperty prop;
+	DEFAULT(p, opt_title);
 
 	if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
 	                                &prop) != Success)
@@ -2148,16 +1639,6 @@ xsettitle(char *p, int pop)
 	XFree(prop.value);
 }
 
-void
-xpushtitle(void)
-{
-	int tstkin = (tstki + 1) % TITLESTACKSIZE;
-
-	free(titlestack[tstkin]);
-	titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
-	tstki = tstkin;
-}
-
 int
 xstartdraw(void)
 {
@@ -2171,16 +1652,18 @@ xdrawline(Line line, int x1, int y1, int x2)
 	Glyph base, new;
 	XftGlyphFontSpec *specs = xw.specbuf;
 
+	numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
 	i = ox = 0;
-	for (x = x1; x < x2; x++) {
+	for (x = x1; x < x2 && i < numspecs; x++) {
 		new = line[x];
 		if (new.mode == ATTR_WDUMMY)
 			continue;
 		if (selected(x, y1))
-			new.mode |= ATTR_SELECTED;
-		if ((i > 0) && ATTRCMP(base, new)) {
-			numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-			xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
+			new.mode ^= ATTR_REVERSE;
+		if (i > 0 && ATTRCMP(base, new)) {
+			xdrawglyphfontspecs(specs, base, i, ox, y1);
+			specs += i;
+			numspecs -= i;
 			i = 0;
 		}
 		if (i == 0) {
@@ -2189,10 +1672,8 @@ xdrawline(Line line, int x1, int y1, int x2)
 		}
 		i++;
 	}
-	if (i > 0) {
-		numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-		xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
-	}
+	if (i > 0)
+		xdrawglyphfontspecs(specs, base, i, ox, y1);
 }
 
 void
@@ -2256,12 +1737,9 @@ xsetmode(int set, unsigned int flags)
 int
 xsetcursor(int cursor)
 {
-	if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
+	if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
 		return 1;
 	win.cursor = cursor;
-	cursorblinks = win.cursor == 0 || win.cursor == 1 ||
-	               win.cursor == 3 || win.cursor == 5 ||
-	               win.cursor == 7;
 	return 0;
 }
 
@@ -2508,10 +1986,6 @@ run(void)
 		if (FD_ISSET(ttyfd, &rfd) || xev) {
 			if (!drawing) {
 				trigger = now;
-				if (IS_SET(MODE_BLINK)) {
-					win.mode ^= MODE_BLINK;
-				}
-				lastblink = now;
 				drawing = 1;
 			}
 			timeout = (maxlatency - TIMEDIFF(now, trigger)) \
@@ -2522,7 +1996,7 @@ run(void)
 
 		/* idle detected or maxlatency exhausted -> draw */
 		timeout = -1;
-		if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) {
+		if (blinktimeout && tattrset(ATTR_BLINK)) {
 			timeout = blinktimeout - TIMEDIFF(now, lastblink);
 			if (timeout <= 0) {
 				if (-timeout > blinktimeout) /* start visible */
@@ -2543,12 +2017,12 @@ run(void)
 void
 usage(void)
 {
-	die("usage: %s [-aiv] [-c class] [-d path] [-f font]"
-	    " [-g geometry] [-n name] [-o file]\n"
+	die("usage: %s [-aiv] [-c class] [-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"
+	    "       %s [-aiv] [-c class] [-f font] [-g geometry]"
+	    " [-n name] [-o file]\n"
 	    "          [-T title] [-t title] [-w windowid] -l line"
 	    " [stty_args ...]\n", argv0, argv0);
 }
@@ -2558,7 +2032,7 @@ main(int argc, char *argv[])
 {
 	xw.l = xw.t = 0;
 	xw.isfixed = False;
-	xsetcursor(cursorstyle);
+	xsetcursor(cursorshape);
 
 	ARGBEGIN {
 	case 'a':
@@ -2600,9 +2074,6 @@ main(int argc, char *argv[])
 	case 'v':
 		die("%s " VERSION "\n", argv0);
 		break;
-	case 'd':
-		opt_dir = EARGF(usage());
-		break;
 	default:
 		usage();
 	} ARGEND;
@@ -2622,7 +2093,6 @@ run:
 	xinit(cols, rows);
 	xsetenv();
 	selinit();
-	chdir(opt_dir);
 	run();
 
 	return 0;

From 58fe6e4d382719d9e1a01ffe51eac99304bf76dd Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:16:47 +0100
Subject: [PATCH 02/13] anysize patch

---
 x.c | 56 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 30 insertions(+), 26 deletions(-)

diff --git a/x.c b/x.c
index b36fb8c..eb06c96 100644
--- a/x.c
+++ b/x.c
@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
 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 */
@@ -331,7 +332,7 @@ ttysend(const Arg *arg)
 int
 evcol(XEvent *e)
 {
-	int x = e->xbutton.x - borderpx;
+	int x = e->xbutton.x - win.hborderpx;
 	LIMIT(x, 0, win.tw - 1);
 	return x / win.cw;
 }
@@ -339,7 +340,7 @@ evcol(XEvent *e)
 int
 evrow(XEvent *e)
 {
-	int y = e->xbutton.y - borderpx;
+	int y = e->xbutton.y - win.vborderpx;
 	LIMIT(y, 0, win.th - 1);
 	return y / win.ch;
 }
@@ -739,6 +740,9 @@ cresize(int width, int height)
 	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);
@@ -869,8 +873,8 @@ xhints(void)
 	sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
 	sizeh->height = win.h;
 	sizeh->width = win.w;
-	sizeh->height_inc = win.ch;
-	sizeh->width_inc = win.cw;
+	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;
@@ -1152,8 +1156,8 @@ xinit(int cols, int rows)
 	xloadcols();
 
 	/* adjust fixed window geometry */
-	win.w = 2 * borderpx + cols * win.cw;
-	win.h = 2 * borderpx + rows * win.ch;
+	win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
+	win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
 	if (xw.gm & XNegative)
 		xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
 	if (xw.gm & YNegative)
@@ -1242,7 +1246,7 @@ xinit(int cols, int rows)
 int
 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
 {
-	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+	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;
@@ -1375,7 +1379,7 @@ void
 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
 {
 	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
-	int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
+	int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
 	    width = charlen * win.cw;
 	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
 	XRenderColor colfg, colbg;
@@ -1465,17 +1469,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
 	/* Intelligent cleaning up of the borders. */
 	if (x == 0) {
-		xclear(0, (y == 0)? 0 : winy, borderpx,
+		xclear(0, (y == 0)? 0 : winy, win.hborderpx,
 			winy + win.ch +
-			((winy + win.ch >= borderpx + win.th)? win.h : 0));
+			((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
 	}
-	if (winx + width >= borderpx + win.tw) {
+	if (winx + width >= win.hborderpx + win.tw) {
 		xclear(winx + width, (y == 0)? 0 : winy, win.w,
-			((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
+			((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
 	}
 	if (y == 0)
-		xclear(winx, 0, winx + width, borderpx);
-	if (winy + win.ch >= borderpx + win.th)
+		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. */
@@ -1569,35 +1573,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
 		case 3: /* Blinking Underline */
 		case 4: /* Steady Underline */
 			XftDrawRect(xw.draw, &drawcol,
-					borderpx + cx * win.cw,
-					borderpx + (cy + 1) * win.ch - \
+					win.hborderpx + cx * win.cw,
+					win.vborderpx + (cy + 1) * win.ch - \
 						cursorthickness,
 					win.cw, cursorthickness);
 			break;
 		case 5: /* Blinking bar */
 		case 6: /* Steady bar */
 			XftDrawRect(xw.draw, &drawcol,
-					borderpx + cx * win.cw,
-					borderpx + cy * win.ch,
+					win.hborderpx + cx * win.cw,
+					win.vborderpx + cy * win.ch,
 					cursorthickness, win.ch);
 			break;
 		}
 	} else {
 		XftDrawRect(xw.draw, &drawcol,
-				borderpx + cx * win.cw,
-				borderpx + cy * win.ch,
+				win.hborderpx + cx * win.cw,
+				win.vborderpx + cy * win.ch,
 				win.cw - 1, 1);
 		XftDrawRect(xw.draw, &drawcol,
-				borderpx + cx * win.cw,
-				borderpx + cy * win.ch,
+				win.hborderpx + cx * win.cw,
+				win.vborderpx + cy * win.ch,
 				1, win.ch - 1);
 		XftDrawRect(xw.draw, &drawcol,
-				borderpx + (cx + 1) * win.cw - 1,
-				borderpx + cy * win.ch,
+				win.hborderpx + (cx + 1) * win.cw - 1,
+				win.vborderpx + cy * win.ch,
 				1, win.ch - 1);
 		XftDrawRect(xw.draw, &drawcol,
-				borderpx + cx * win.cw,
-				borderpx + (cy + 1) * win.ch - 1,
+				win.hborderpx + cx * win.cw,
+				win.vborderpx + (cy + 1) * win.ch - 1,
 				win.cw, 1);
 	}
 }

From f67e9b335fd0cc02b0f2d1782bfeabbf54818dc0 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:29:43 +0100
Subject: [PATCH 03/13] blinking cursor patch

---
 config.def.h                                  | 19 +++++---
 ... st-blinking_cursor-20231213-9846a56.diff} | 30 ++++--------
 x.c                                           | 47 ++++++++++++++-----
 3 files changed, 57 insertions(+), 39 deletions(-)
 rename patches/{st-blinking_cursor-20211116-2f6e597.diff => st-blinking_cursor-20231213-9846a56.diff} (81%)

diff --git a/config.def.h b/config.def.h
index 91ab8ca..b33a2ac 100644
--- a/config.def.h
+++ b/config.def.h
@@ -135,13 +135,20 @@ unsigned int defaultcs = 256;
 static unsigned int defaultrcs = 257;
 
 /*
- * Default shape of cursor
- * 2: Block ("█")
- * 4: Underline ("_")
- * 6: Bar ("|")
- * 7: Snowman ("☃")
+ * 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 cursorshape = 2;
+static unsigned int cursorstyle = 1;
+static Rune stcursor = 0x2603; /* snowman ("☃") */
 
 /*
  * Default columns and rows numbers
diff --git a/patches/st-blinking_cursor-20211116-2f6e597.diff b/patches/st-blinking_cursor-20231213-9846a56.diff
similarity index 81%
rename from patches/st-blinking_cursor-20211116-2f6e597.diff
rename to patches/st-blinking_cursor-20231213-9846a56.diff
index 91c871a..47fdeec 100644
--- a/patches/st-blinking_cursor-20211116-2f6e597.diff
+++ b/patches/st-blinking_cursor-20231213-9846a56.diff
@@ -1,18 +1,8 @@
-From a3cdd0753bf578cd4e6db7c6507481f3b5c38aea Mon Sep 17 00:00:00 2001
-From: Steve Ward <planet36@gmail.com>
-Date: Tue, 16 Nov 2021 14:15:06 -0500
-Subject: [PATCH] Allow blinking cursor
-
----
- config.def.h | 19 +++++++++++++------
- x.c          | 47 +++++++++++++++++++++++++++++++++++------------
- 2 files changed, 48 insertions(+), 18 deletions(-)
-
 diff --git a/config.def.h b/config.def.h
-index 6f05dce..1a5fed0 100644
+index 91ab8ca..b33a2ac 100644
 --- a/config.def.h
 +++ b/config.def.h
-@@ -133,13 +133,20 @@ static unsigned int defaultcs = 256;
+@@ -135,13 +135,20 @@ unsigned int defaultcs = 256;
  static unsigned int defaultrcs = 257;
  
  /*
@@ -40,18 +30,18 @@ index 6f05dce..1a5fed0 100644
  /*
   * Default columns and rows numbers
 diff --git a/x.c b/x.c
-index 89786b8..7d2447d 100644
+index b36fb8c..a7eb96a 100644
 --- a/x.c
 +++ b/x.c
 @@ -253,6 +253,7 @@ static char *opt_name  = NULL;
  static char *opt_title = NULL;
  
- static int oldbutton = 3; /* button event on startup: 3 = release */
+ static uint buttons; /* bit field of pressed buttons */
 +static int cursorblinks = 0;
  
  void
  clipcopy(const Arg *dummy)
-@@ -1529,29 +1530,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+@@ -1558,29 +1559,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
  	/* draw the new one */
  	if (IS_SET(MODE_FOCUSED)) {
  		switch (win.cursor) {
@@ -105,7 +95,7 @@ index 89786b8..7d2447d 100644
  		}
  	} else {
  		XftDrawRect(xw.draw, &drawcol,
-@@ -1708,9 +1724,12 @@ xsetmode(int set, unsigned int flags)
+@@ -1737,9 +1753,12 @@ xsetmode(int set, unsigned int flags)
  int
  xsetcursor(int cursor)
  {
@@ -119,7 +109,7 @@ index 89786b8..7d2447d 100644
  	return 0;
  }
  
-@@ -1954,6 +1973,10 @@ run(void)
+@@ -1986,6 +2005,10 @@ run(void)
  		if (FD_ISSET(ttyfd, &rfd) || xev) {
  			if (!drawing) {
  				trigger = now;
@@ -130,7 +120,7 @@ index 89786b8..7d2447d 100644
  				drawing = 1;
  			}
  			timeout = (maxlatency - TIMEDIFF(now, trigger)) \
-@@ -1964,7 +1987,7 @@ run(void)
+@@ -1996,7 +2019,7 @@ run(void)
  
  		/* idle detected or maxlatency exhausted -> draw */
  		timeout = -1;
@@ -139,7 +129,7 @@ index 89786b8..7d2447d 100644
  			timeout = blinktimeout - TIMEDIFF(now, lastblink);
  			if (timeout <= 0) {
  				if (-timeout > blinktimeout) /* start visible */
-@@ -2000,7 +2023,7 @@ main(int argc, char *argv[])
+@@ -2032,7 +2055,7 @@ main(int argc, char *argv[])
  {
  	xw.l = xw.t = 0;
  	xw.isfixed = False;
@@ -148,6 +138,4 @@ index 89786b8..7d2447d 100644
  
  	ARGBEGIN {
  	case 'a':
--- 
-2.34.0
 
diff --git a/x.c b/x.c
index eb06c96..3da01c9 100644
--- a/x.c
+++ b/x.c
@@ -254,6 +254,7 @@ static char *opt_name  = NULL;
 static char *opt_title = NULL;
 
 static uint buttons; /* bit field of pressed buttons */
+static int cursorblinks = 0;
 
 void
 clipcopy(const Arg *dummy)
@@ -1562,29 +1563,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
 	/* draw the new one */
 	if (IS_SET(MODE_FOCUSED)) {
 		switch (win.cursor) {
-		case 7: /* st extension */
-			g.u = 0x2603; /* snowman (U+2603) */
+		default:
+		case 0: /* blinking block */
+		case 1: /* blinking block (default) */
+			if (IS_SET(MODE_BLINK))
+				break;
 			/* FALLTHROUGH */
-		case 0: /* Blinking Block */
-		case 1: /* Blinking Block (Default) */
-		case 2: /* Steady Block */
+		case 2: /* steady block */
 			xdrawglyph(g, cx, cy);
 			break;
-		case 3: /* Blinking Underline */
-		case 4: /* Steady Underline */
+		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 */
-		case 6: /* Steady bar */
+		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,
@@ -1741,9 +1757,12 @@ xsetmode(int set, unsigned int flags)
 int
 xsetcursor(int cursor)
 {
-	if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
+	if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
 		return 1;
 	win.cursor = cursor;
+	cursorblinks = win.cursor == 0 || win.cursor == 1 ||
+	               win.cursor == 3 || win.cursor == 5 ||
+	               win.cursor == 7;
 	return 0;
 }
 
@@ -1990,6 +2009,10 @@ run(void)
 		if (FD_ISSET(ttyfd, &rfd) || xev) {
 			if (!drawing) {
 				trigger = now;
+				if (IS_SET(MODE_BLINK)) {
+					win.mode ^= MODE_BLINK;
+				}
+				lastblink = now;
 				drawing = 1;
 			}
 			timeout = (maxlatency - TIMEDIFF(now, trigger)) \
@@ -2000,7 +2023,7 @@ run(void)
 
 		/* idle detected or maxlatency exhausted -> draw */
 		timeout = -1;
-		if (blinktimeout && tattrset(ATTR_BLINK)) {
+		if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) {
 			timeout = blinktimeout - TIMEDIFF(now, lastblink);
 			if (timeout <= 0) {
 				if (-timeout > blinktimeout) /* start visible */
@@ -2036,7 +2059,7 @@ main(int argc, char *argv[])
 {
 	xw.l = xw.t = 0;
 	xw.isfixed = False;
-	xsetcursor(cursorshape);
+	xsetcursor(cursorstyle);
 
 	ARGBEGIN {
 	case 'a':

From c774b5fd4d82dc93d9f45dcb27bfe2102f35909b Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:30:45 +0100
Subject: [PATCH 04/13] bold is not bright patch

---
 x.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/x.c b/x.c
index 3da01c9..0bf3e34 100644
--- a/x.c
+++ b/x.c
@@ -1417,10 +1417,6 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 		bg = &dc.col[base.bg];
 	}
 
-	/* Change basic system colors [0-7] to bright system colors [8-15] */
-	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
-		fg = &dc.col[base.fg + 8];
-
 	if (IS_SET(MODE_REVERSE)) {
 		if (fg == &dc.col[defaultfg]) {
 			fg = &dc.col[defaultbg];

From 820a847e07b65a7c9b321d2356f6d44ab49b147b Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:32:34 +0100
Subject: [PATCH 05/13] boxdraw patch

---
 Makefile       |   3 +-
 boxdraw.c      | 194 ++++++++++++++++++++++++++++++++++++++++++++
 boxdraw_data.h | 214 +++++++++++++++++++++++++++++++++++++++++++++++++
 config.def.h   |  12 +++
 st.c           |   3 +
 st.h           |  10 +++
 x.c            |  21 +++--
 7 files changed, 451 insertions(+), 6 deletions(-)
 create mode 100644 boxdraw.c
 create mode 100644 boxdraw_data.h

diff --git a/Makefile b/Makefile
index 15db421..a64b4c2 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 
 include config.mk
 
-SRC = st.c x.c
+SRC = st.c x.c boxdraw.c
 OBJ = $(SRC:.c=.o)
 
 all: st
@@ -17,6 +17,7 @@ config.h:
 
 st.o: config.h st.h win.h
 x.o: arg.h config.h st.h win.h
+boxdraw.o: config.h st.h boxdraw_data.h
 
 $(OBJ): config.h config.mk
 
diff --git a/boxdraw.c b/boxdraw.c
new file mode 100644
index 0000000..28a92d0
--- /dev/null
+++ b/boxdraw.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
+ * MIT/X Consortium License
+ */
+
+#include <X11/Xft/Xft.h>
+#include "st.h"
+#include "boxdraw_data.h"
+
+/* 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/boxdraw_data.h b/boxdraw_data.h
new file mode 100644
index 0000000..7890500
--- /dev/null
+++ b/boxdraw_data.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/config.def.h b/config.def.h
index b33a2ac..9ad34b5 100644
--- a/config.def.h
+++ b/config.def.h
@@ -67,6 +67,18 @@ static unsigned int blinktimeout = 800;
  */
 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
diff --git a/st.c b/st.c
index d6478f5..c4e373a 100644
--- a/st.c
+++ b/st.c
@@ -1215,6 +1215,9 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 	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
diff --git a/st.h b/st.h
index fd3b0d8..808f5f7 100644
--- a/st.h
+++ b/st.h
@@ -33,6 +33,7 @@ enum glyph_attribute {
 	ATTR_WRAP       = 1 << 8,
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
+	ATTR_BOXDRAW    = 1 << 11,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
 };
 
@@ -111,6 +112,14 @@ void *xmalloc(size_t);
 void *xrealloc(void *, size_t);
 char *xstrdup(const char *);
 
+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
+
 /* config.h globals */
 extern char *utmp;
 extern char *scroll;
@@ -124,3 +133,4 @@ extern unsigned int tabspaces;
 extern unsigned int defaultfg;
 extern unsigned int defaultbg;
 extern unsigned int defaultcs;
+extern const int boxdraw, boxdraw_bold, boxdraw_braille;
diff --git a/x.c b/x.c
index 0bf3e34..79d75e3 100644
--- a/x.c
+++ b/x.c
@@ -1242,6 +1242,8 @@ xinit(int cols, int rows)
 	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);
 }
 
 int
@@ -1288,8 +1290,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 			yp = winy + font->ascent;
 		}
 
-		/* Lookup character index with default font. */
-		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+		if (mode & ATTR_BOXDRAW) {
+			/* minor shoehorning: boxdraw uses only this ushort */
+			glyphidx = boxdrawindex(&glyphs[i]);
+		} else {
+			/* Lookup character index with default font. */
+			glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+		}
 		if (glyphidx) {
 			specs[numspecs].font = font->match;
 			specs[numspecs].glyph = glyphidx;
@@ -1489,8 +1496,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	r.width = width;
 	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
 
-	/* Render the glyphs. */
-	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+	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) {
@@ -1533,7 +1544,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
 	/*
 	 * Select the right color for the right mode.
 	 */
-	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
+	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
 
 	if (IS_SET(MODE_REVERSE)) {
 		g.mode |= ATTR_REVERSE;

From c9d724dad5e4f6aa4a87eaf0df0656dd2a04c29a Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:33:08 +0100
Subject: [PATCH 06/13] csi patch

---
 st.c    | 36 ++++++++++++++++++++++++++++++++----
 st.info |  4 ++--
 win.h   |  4 +++-
 x.c     | 41 ++++++++++++++++++++++++++++++++++++++---
 4 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/st.c b/st.c
index c4e373a..b480ddb 100644
--- a/st.c
+++ b/st.c
@@ -1812,6 +1812,33 @@ csihandle(void)
 			goto unknown;
 		}
 		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;
+		}
 	}
 }
 
@@ -1890,7 +1917,7 @@ strhandle(void)
 		switch (par) {
 		case 0:
 			if (narg > 1) {
-				xsettitle(strescseq.args[1]);
+				xsettitle(strescseq.args[1], 0);
 				xseticontitle(strescseq.args[1]);
 			}
 			return;
@@ -1900,7 +1927,7 @@ strhandle(void)
 			return;
 		case 2:
 			if (narg > 1)
-				xsettitle(strescseq.args[1]);
+				xsettitle(strescseq.args[1], 0);
 			return;
 		case 52:
 			if (narg > 2 && allowwindowops) {
@@ -1959,7 +1986,7 @@ strhandle(void)
 		}
 		break;
 	case 'k': /* old title set compatibility */
-		xsettitle(strescseq.args[0]);
+		xsettitle(strescseq.args[0], 0);
 		return;
 	case 'P': /* DCS -- Device Control String */
 	case '_': /* APC -- Application Program Command */
@@ -2331,6 +2358,7 @@ eschandle(uchar ascii)
 		break;
 	case 'c': /* RIS -- Reset to initial state */
 		treset();
+		xfreetitlestack();
 		resettitle();
 		xloadcols();
 		xsetmode(0, MODE_HIDE);
@@ -2626,7 +2654,7 @@ tresize(int col, int row)
 void
 resettitle(void)
 {
-	xsettitle(NULL);
+	xsettitle(NULL, 0);
 }
 
 void
diff --git a/st.info b/st.info
index efab2cf..24dfc7d 100644
--- a/st.info
+++ b/st.info
@@ -161,7 +161,7 @@ st-mono| simpleterm monocolor,
 	rin=\E[%p1%dT,
 	ritm=\E[23m,
 	rmacs=\E(B,
-	rmcup=\E[?1049l,
+	rmcup=\E[?1049l\E[23;0;0t,
 	rmir=\E[4l,
 	rmkx=\E[?1l\E>,
 	rmso=\E[27m,
@@ -172,7 +172,7 @@ st-mono| simpleterm monocolor,
 	sitm=\E[3m,
 	sgr0=\E[0m,
 	smacs=\E(0,
-	smcup=\E[?1049h,
+	smcup=\E[?1049h\E[22;0;0t,
 	smir=\E[4h,
 	smkx=\E[?1h\E=,
 	smso=\E[7m,
diff --git a/win.h b/win.h
index 6de960d..2a40aa0 100644
--- a/win.h
+++ b/win.h
@@ -32,7 +32,9 @@ void xloadcols(void);
 int xsetcolorname(int, const char *);
 int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
 void xseticontitle(char *);
-void xsettitle(char *);
+void xfreetitlestack(void);
+void xsettitle(char *, int);
+void xpushtitle(void);
 int xsetcursor(int);
 void xsetmode(int, unsigned int);
 void xsetpointermotion(int);
diff --git a/x.c b/x.c
index 79d75e3..43911a8 100644
--- a/x.c
+++ b/x.c
@@ -63,6 +63,9 @@ static void ttysend(const Arg *);
 /* 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
@@ -221,6 +224,8 @@ static DC dc;
 static XWindow xw;
 static XSelection xsel;
 static TermWindow win;
+static int tstki; /* title stack index */
+static char *titlestack[TITLESTACKSIZE]; /* title stack */
 
 /* Font Ring Cache */
 enum {
@@ -1653,10 +1658,30 @@ xseticontitle(char *p)
 }
 
 void
-xsettitle(char *p)
+xfreetitlestack(void)
 {
-	XTextProperty prop;
-	DEFAULT(p, opt_title);
+	for (int i = 0; i < LEN(titlestack); i++) {
+		free(titlestack[i]);
+		titlestack[i] = NULL;
+	}
+}
+
+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)
@@ -1666,6 +1691,16 @@ xsettitle(char *p)
 	XFree(prop.value);
 }
 
+void
+xpushtitle(void)
+{
+	int tstkin = (tstki + 1) % TITLESTACKSIZE;
+
+	free(titlestack[tstkin]);
+	titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
+	tstki = tstkin;
+}
+
 int
 xstartdraw(void)
 {

From 42f4340730cecbbba276eb5b95e4cabbc3346a78 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:33:48 +0100
Subject: [PATCH 07/13] ligatures patch

---
 Makefile                                      |   5 +-
 config.mk                                     |   6 +-
 hb.c                                          | 124 ++++
 hb.h                                          |  14 +
 .../st-ligatures-boxdraw-20230105-0.9.diff    | 624 ------------------
 ...st-ligatures-boxdraw-20231213-9846a56.diff | 101 +++
 st.c                                          |   3 +-
 st.h                                          |   3 +-
 win.h                                         |   2 +-
 x.c                                           | 290 ++++----
 10 files changed, 424 insertions(+), 748 deletions(-)
 create mode 100644 hb.c
 create mode 100644 hb.h
 delete mode 100644 patches/st-ligatures-boxdraw-20230105-0.9.diff
 create mode 100644 patches/st-ligatures-boxdraw-20231213-9846a56.diff

diff --git a/Makefile b/Makefile
index a64b4c2..05124bf 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 
 include config.mk
 
-SRC = st.c x.c boxdraw.c
+SRC = st.c x.c boxdraw.c hb.c
 OBJ = $(SRC:.c=.o)
 
 all: st
@@ -16,8 +16,9 @@ config.h:
 	$(CC) $(STCFLAGS) -c $<
 
 st.o: config.h st.h win.h
-x.o: arg.h config.h st.h win.h
+x.o: arg.h config.h st.h win.h hb.h
 boxdraw.o: config.h st.h boxdraw_data.h
+hb.o: st.h
 
 $(OBJ): config.h config.mk
 
diff --git a/config.mk b/config.mk
index 1e306f8..3e13e53 100644
--- a/config.mk
+++ b/config.mk
@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
 # includes and libs
 INCS = -I$(X11INC) \
        `$(PKG_CONFIG) --cflags fontconfig` \
-       `$(PKG_CONFIG) --cflags freetype2`
+       `$(PKG_CONFIG) --cflags freetype2` \
+       `$(PKG_CONFIG) --cflags harfbuzz`
 LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
        `$(PKG_CONFIG) --libs fontconfig` \
-       `$(PKG_CONFIG) --libs freetype2`
+       `$(PKG_CONFIG) --libs freetype2` \
+       `$(PKG_CONFIG) --libs harfbuzz`
 
 # flags
 STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
diff --git a/hb.c b/hb.c
new file mode 100644
index 0000000..b42f357
--- /dev/null
+++ b/hb.c
@@ -0,0 +1,124 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <X11/Xft/Xft.h>
+#include <X11/cursorfont.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+#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 }
+#define BUFFER_STEP 256
+
+hb_font_t *hbfindfont(XftFont *match);
+
+typedef struct {
+	XftFont *match;
+	hb_font_t *font;
+} HbFontMatch;
+
+typedef struct {
+	size_t capacity;
+	HbFontMatch *fonts;
+} HbFontCache;
+
+static HbFontCache hbfontcache = { 0, NULL };
+
+typedef struct {
+	size_t capacity;
+	Rune *runes;
+} RuneBuffer;
+
+static RuneBuffer hbrunebuffer = { 0, 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[] = { };
+
+void
+hbunloadfonts()
+{
+	for (int i = 0; i < hbfontcache.capacity; i++) {
+		hb_font_destroy(hbfontcache.fonts[i].font);
+		XftUnlockFace(hbfontcache.fonts[i].match);
+	}
+
+	if (hbfontcache.fonts != NULL) {
+		free(hbfontcache.fonts);
+		hbfontcache.fonts = NULL;
+	}
+	hbfontcache.capacity = 0;
+}
+
+hb_font_t *
+hbfindfont(XftFont *match)
+{
+	for (int i = 0; i < hbfontcache.capacity; i++) {
+		if (hbfontcache.fonts[i].match == match)
+			return hbfontcache.fonts[i].font;
+	}
+
+	/* Font not found in cache, caching it now. */
+	hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 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.fonts[hbfontcache.capacity].match = match;
+	hbfontcache.fonts[hbfontcache.capacity].font = font;
+	hbfontcache.capacity += 1;
+
+	return font;
+}
+
+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
+	ushort mode = USHRT_MAX;
+	unsigned int glyph_count;
+	int rune_idx, glyph_idx, 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);
+
+	/* Resize the buffer if required length is larger. */
+	if (hbrunebuffer.capacity < length) {
+		hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
+		hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
+	}
+
+	/* Fill buffer with codepoints. */
+	for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
+		hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
+		mode = glyphs[glyph_idx].mode;
+		if (mode & ATTR_WDUMMY)
+			hbrunebuffer.runes[rune_idx] = 0x0020;
+	}
+	hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
+
+	/* 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..88de9bd
--- /dev/null
+++ b/hb.h
@@ -0,0 +1,14 @@
+#include <X11/Xft/Xft.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+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/patches/st-ligatures-boxdraw-20230105-0.9.diff b/patches/st-ligatures-boxdraw-20230105-0.9.diff
deleted file mode 100644
index 20c78f9..0000000
--- a/patches/st-ligatures-boxdraw-20230105-0.9.diff
+++ /dev/null
@@ -1,624 +0,0 @@
-diff --git a/Makefile b/Makefile
-index 6dfa212..adfa07a 100644
---- a/Makefile
-+++ b/Makefile
-@@ -4,7 +4,7 @@
- 
- include config.mk
- 
--SRC = st.c x.c boxdraw.c
-+SRC = st.c x.c boxdraw.c hb.c
- OBJ = $(SRC:.c=.o)
- 
- all: options st
-@@ -22,8 +22,9 @@ config.h:
- 	$(CC) $(STCFLAGS) -c $<
- 
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
- boxdraw.o: config.h st.h boxdraw_data.h
-+hb.o: st.h
- 
- $(OBJ): config.h config.mk
- 
-diff --git a/config.mk b/config.mk
-index 1e306f8..3e13e53 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
-        `$(PKG_CONFIG) --cflags fontconfig` \
--       `$(PKG_CONFIG) --cflags freetype2`
-+       `$(PKG_CONFIG) --cflags freetype2` \
-+       `$(PKG_CONFIG) --cflags harfbuzz`
- LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
-        `$(PKG_CONFIG) --libs fontconfig` \
--       `$(PKG_CONFIG) --libs freetype2`
-+       `$(PKG_CONFIG) --libs freetype2` \
-+       `$(PKG_CONFIG) --libs harfbuzz`
- 
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#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 }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+	XftFont *match;
-+	hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+	size_t capacity;
-+	HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+	size_t capacity;
-+	Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, 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[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+	for (int i = 0; i < hbfontcache.capacity; i++) {
-+		hb_font_destroy(hbfontcache.fonts[i].font);
-+		XftUnlockFace(hbfontcache.fonts[i].match);
-+	}
-+
-+	if (hbfontcache.fonts != NULL) {
-+		free(hbfontcache.fonts);
-+		hbfontcache.fonts = NULL;
-+	}
-+	hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+	for (int i = 0; i < hbfontcache.capacity; i++) {
-+		if (hbfontcache.fonts[i].match == match)
-+			return hbfontcache.fonts[i].font;
-+	}
-+
-+	/* Font not found in cache, caching it now. */
-+	hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 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.fonts[hbfontcache.capacity].match = match;
-+	hbfontcache.fonts[hbfontcache.capacity].font = font;
-+	hbfontcache.capacity += 1;
-+
-+	return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+	ushort mode = USHRT_MAX;
-+	unsigned int glyph_count;
-+	int rune_idx, glyph_idx, 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);
-+
-+	/* Resize the buffer if required length is larger. */
-+	if (hbrunebuffer.capacity < length) {
-+		hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+		hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+	}
-+
-+	/* Fill buffer with codepoints. */
-+	for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+		hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+		mode = glyphs[glyph_idx].mode;
-+		if (mode & ATTR_WDUMMY)
-+			hbrunebuffer.runes[rune_idx] = 0x0020;
-+	}
-+	hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+	/* 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..88de9bd
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+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/st.c b/st.c
-index 41d5ace..1c2edd6 100644
---- a/st.c
-+++ b/st.c
-@@ -2643,7 +2643,8 @@ draw(void)
- 
- 	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.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+			term.line[term.ocy], term.col);
- 	term.ocx = cx;
- 	term.ocy = term.c.y;
- 	xfinishdraw();
-diff --git a/st.h b/st.h
-index 808f5f7..ae41368 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #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 != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b)		(((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+				(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)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
- 
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index bf6bbf9..929a59a 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
- 
- /* types used in config.h */
- typedef struct {
-@@ -141,6 +142,7 @@ typedef struct {
- } DC;
- 
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
- static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
- static void xdrawglyph(Glyph, int, int);
-@@ -757,7 +759,7 @@ xresize(int col, int row)
- 	xclear(0, 0, win.w, win.h);
- 
- 	/* resize to new width */
--	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
- 
- ushort
-@@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
- 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);
-@@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
- 	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
- 
- 	/* font spec buffer */
--	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
- 
- 	/* Xft rendering context */
- 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1241,6 +1246,22 @@ xinit(int cols, int rows)
- 	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)
- {
-@@ -1255,126 +1276,158 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- 	FcPattern *fcpattern, *fontpattern;
- 	FcFontSet *fcsets[] = { NULL };
- 	FcCharSet *fccharset;
--	int i, f, numspecs = 0;
-+	int i, f, length = 0, start = 0, numspecs = 0;
-+	float cluster_xp = xp, cluster_yp = yp;
-+	HbTransformData shaped = { 0 };
-+
-+	/* Initial values. */
-+	mode = prevmode = glyphs[0].mode;
-+	xresetfontsettings(mode, &font, &frcflags);
- 
- 	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
--		/* Fetch rune and mode for current glyph. */
--		rune = glyphs[i].u;
- 		mode = glyphs[i].mode;
- 
- 		/* Skip dummy wide-character spacing. */
--		if (mode == ATTR_WDUMMY)
-+		if (mode & ATTR_WDUMMY && i < (len - 1))
- 			continue;
- 
--		/* Determine font for glyph if different from previous glyph. */
--		if (prevmode != mode) {
--			prevmode = mode;
--			font = &dc.font;
--			frcflags = FRC_NORMAL;
--			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
--			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;
-+		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);
- 			}
--			yp = winy + font->ascent;
--		}
- 
--		if (mode & ATTR_BOXDRAW) {
--			/* minor shoehorning: boxdraw uses only this ushort */
--			glyphidx = boxdrawindex(&glyphs[i]);
--		} else {
--			/* Lookup character index with default font. */
--			glyphidx = XftCharIndex(xw.dpy, font->match, rune);
--		}
--		if (glyphidx) {
--			specs[numspecs].font = font->match;
--			specs[numspecs].glyph = glyphidx;
--			specs[numspecs].x = (short)xp;
--			specs[numspecs].y = (short)yp;
--			xp += runewidth;
--			numspecs++;
--			continue;
--		}
--
--		/* Fallback on font cache, search the font cache for match. */
--		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;
-+			/* Shape the segment. */
-+			hbtransform(&shaped, font->match, glyphs, start, length);
-+			runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+			cluster_xp = xp; cluster_yp = yp;
-+			for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+				int idx = shaped.glyphs[code_idx].cluster;
-+
-+				if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+					continue;
-+
-+				/* Advance the drawing cursor if we've moved to a new cluster */
-+				if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+					xp += runewidth;
-+					cluster_xp = xp;
-+					cluster_yp = yp;
-+					runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+				}
-+
-+				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;
-+					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 = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+					specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+					cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+					cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+					numspecs++;
-+				} else {
-+					/* If it's not found, try to fetch it through the font cache. */
-+					rune = glyphs[start + idx].u;
-+					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;
-+					numspecs++;
-+				}
- 			}
--		}
- 
--		/* 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;
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			/*
--			 * 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));
-+			/* Determine font for glyph if different from previous glyph. */
-+			if (prevmode != mode) {
-+				prevmode = mode;
-+				xresetfontsettings(mode, &font, &frcflags);
-+				yp = winy + font->ascent;
- 			}
--
--			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++;
- 	}
- 
-+	hbcleanup(&shaped);
- 	return numspecs;
- }
- 
-@@ -1528,14 +1581,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+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;
--	xdrawglyph(og, ox, oy);
-+
-+	/* 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))
- 		return;
-@@ -1663,18 +1719,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- 	Glyph base, new;
- 	XftGlyphFontSpec *specs = xw.specbuf;
- 
--	numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- 	i = ox = 0;
--	for (x = x1; x < x2 && i < numspecs; x++) {
-+	for (x = x1; x < x2; 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);
--			specs += i;
--			numspecs -= i;
-+		if ((i > 0) && ATTRCMP(base, new)) {
-+			numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+			xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
- 			i = 0;
- 		}
- 		if (i == 0) {
-@@ -1683,8 +1737,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- 		}
- 		i++;
- 	}
--	if (i > 0)
--		xdrawglyphfontspecs(specs, base, i, ox, y1);
-+	if (i > 0) {
-+		numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+		xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
-+	}
- }
- 
- void
diff --git a/patches/st-ligatures-boxdraw-20231213-9846a56.diff b/patches/st-ligatures-boxdraw-20231213-9846a56.diff
new file mode 100644
index 0000000..7d9620c
--- /dev/null
+++ b/patches/st-ligatures-boxdraw-20231213-9846a56.diff
@@ -0,0 +1,101 @@
+diff --git a/config.def.h b/config.def.h
+index 91ab8ca..47b7915 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -127,12 +127,17 @@ static const char *colorname[] = {
+ 
+ /*
+  * Default colors (colorname index)
+- * foreground, background, cursor, reverse cursor
++ * foreground, background, cursor, reverse cursor, selection
+  */
+ unsigned int defaultfg = 258;
+ unsigned int defaultbg = 259;
+ unsigned int defaultcs = 256;
+-static unsigned int defaultrcs = 257;
++static unsigned int defaultrcs = 256;
++unsigned int selectionbg = 257;
++unsigned int selectionfg = 7;
++/* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
++/* Else if 1 keep original foreground-color of each cell => more colors :) */
++static int ignoreselfg = 1;
+ 
+ /*
+  * Default shape of cursor
+diff --git a/st.h b/st.h
+index fd3b0d8..1d56e54 100644
+--- a/st.h
++++ b/st.h
+@@ -33,6 +33,7 @@ enum glyph_attribute {
+ 	ATTR_WRAP       = 1 << 8,
+ 	ATTR_WIDE       = 1 << 9,
+ 	ATTR_WDUMMY     = 1 << 10,
++	ATTR_SELECTED   = 1 << 11,
+ 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+ };
+ 
+diff --git a/x.c b/x.c
+index b36fb8c..bba8be5 100644
+--- a/x.c
++++ b/x.c
+@@ -1457,6 +1457,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 		bg = temp;
+ 	}
+ 
++	if (base.mode & ATTR_SELECTED) {
++		bg = &dc.col[selectionbg];
++		if (!ignoreselfg)
++			fg = &dc.col[selectionfg];
++	}
++
+ 	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
+ 		fg = bg;
+ 
+@@ -1523,7 +1529,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+ 
+ 	/* remove the old cursor */
+ 	if (selected(ox, oy))
+-		og.mode ^= ATTR_REVERSE;
++		og.mode |= ATTR_SELECTED;
+ 	xdrawglyph(og, ox, oy);
+ 
+ 	if (IS_SET(MODE_HIDE))
+@@ -1536,23 +1542,13 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+ 
+ 	if (IS_SET(MODE_REVERSE)) {
+ 		g.mode |= ATTR_REVERSE;
++		g.fg = defaultcs;
+ 		g.bg = defaultfg;
+-		if (selected(cx, cy)) {
+-			drawcol = dc.col[defaultcs];
+-			g.fg = defaultrcs;
+-		} else {
+-			drawcol = dc.col[defaultrcs];
+-			g.fg = defaultcs;
+-		}
++		drawcol = dc.col[defaultrcs];
+ 	} else {
+-		if (selected(cx, cy)) {
+-			g.fg = defaultfg;
+-			g.bg = defaultrcs;
+-		} else {
+-			g.fg = defaultbg;
+-			g.bg = defaultcs;
+-		}
+-		drawcol = dc.col[g.bg];
++		g.fg = defaultbg;
++		g.bg = defaultcs;
++		drawcol = dc.col[defaultcs];
+ 	}
+ 
+ 	/* draw the new one */
+@@ -1659,7 +1655,7 @@ xdrawline(Line line, int x1, int y1, int x2)
+ 		if (new.mode == ATTR_WDUMMY)
+ 			continue;
+ 		if (selected(x, y1))
+-			new.mode ^= ATTR_REVERSE;
++			new.mode |= ATTR_SELECTED;
+ 		if (i > 0 && ATTRCMP(base, new)) {
+ 			xdrawglyphfontspecs(specs, base, i, ox, y1);
+ 			specs += i;
+
diff --git a/st.c b/st.c
index b480ddb..c2e0880 100644
--- a/st.c
+++ b/st.c
@@ -2689,7 +2689,8 @@ draw(void)
 
 	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.ocx, term.ocy, term.line[term.ocy][term.ocx],
+			term.line[term.ocy], term.col);
 	term.ocx = cx;
 	term.ocy = term.c.y;
 	xfinishdraw();
diff --git a/st.h b/st.h
index 808f5f7..ae41368 100644
--- a/st.h
+++ b/st.h
@@ -11,7 +11,8 @@
 #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 != (b).mode || (a).fg != (b).fg || \
+#define ATTRCMP(a, b)		(((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
+				(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)
diff --git a/win.h b/win.h
index 2a40aa0..8712eea 100644
--- a/win.h
+++ b/win.h
@@ -25,7 +25,7 @@ enum win_mode {
 
 void xbell(void);
 void xclipcopy(void);
-void xdrawcursor(int, int, Glyph, int, int, Glyph);
+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
 void xdrawline(Line, int, int, int);
 void xfinishdraw(void);
 void xloadcols(void);
diff --git a/x.c b/x.c
index 43911a8..0915226 100644
--- a/x.c
+++ b/x.c
@@ -19,6 +19,7 @@ char *argv0;
 #include "arg.h"
 #include "st.h"
 #include "win.h"
+#include "hb.h"
 
 /* types used in config.h */
 typedef struct {
@@ -145,6 +146,7 @@ typedef struct {
 } DC;
 
 static inline ushort sixd_to_16bit(int);
+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
 static void xdrawglyph(Glyph, int, int);
@@ -767,7 +769,7 @@ xresize(int col, int row)
 	xclear(0, 0, win.w, win.h);
 
 	/* resize to new width */
-	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
 }
 
 ushort
@@ -1072,6 +1074,9 @@ xunloadfont(Font *f)
 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);
@@ -1195,7 +1200,7 @@ xinit(int cols, int rows)
 	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
 
 	/* font spec buffer */
-	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
 
 	/* Xft rendering context */
 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@@ -1251,6 +1256,22 @@ xinit(int cols, int rows)
 	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)
 {
@@ -1265,126 +1286,158 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 	FcPattern *fcpattern, *fontpattern;
 	FcFontSet *fcsets[] = { NULL };
 	FcCharSet *fccharset;
-	int i, f, numspecs = 0;
+	int i, f, length = 0, start = 0, numspecs = 0;
+	float cluster_xp = xp, cluster_yp = yp;
+	HbTransformData shaped = { 0 };
+
+	/* Initial values. */
+	mode = prevmode = glyphs[0].mode;
+	xresetfontsettings(mode, &font, &frcflags);
 
 	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-		/* Fetch rune and mode for current glyph. */
-		rune = glyphs[i].u;
 		mode = glyphs[i].mode;
 
 		/* Skip dummy wide-character spacing. */
-		if (mode == ATTR_WDUMMY)
+		if (mode & ATTR_WDUMMY && i < (len - 1))
 			continue;
 
-		/* Determine font for glyph if different from previous glyph. */
-		if (prevmode != mode) {
-			prevmode = mode;
-			font = &dc.font;
-			frcflags = FRC_NORMAL;
-			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-			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;
-			}
-			yp = winy + font->ascent;
-		}
-
-		if (mode & ATTR_BOXDRAW) {
-			/* minor shoehorning: boxdraw uses only this ushort */
-			glyphidx = boxdrawindex(&glyphs[i]);
-		} else {
-			/* Lookup character index with default font. */
-			glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-		}
-		if (glyphidx) {
-			specs[numspecs].font = font->match;
-			specs[numspecs].glyph = glyphidx;
-			specs[numspecs].x = (short)xp;
-			specs[numspecs].y = (short)yp;
-			xp += runewidth;
-			numspecs++;
-			continue;
-		}
-
-		/* Fallback on font cache, search the font cache for match. */
-		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));
+		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);
 			}
 
-			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;
+			/* Shape the segment. */
+			hbtransform(&shaped, font->match, glyphs, start, length);
+			runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+			cluster_xp = xp; cluster_yp = yp;
+			for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
+				int idx = shaped.glyphs[code_idx].cluster;
 
-			glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+				if (glyphs[start + idx].mode & ATTR_WDUMMY)
+					continue;
 
-			f = frclen;
-			frclen++;
+				/* Advance the drawing cursor if we've moved to a new cluster */
+				if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
+					xp += runewidth;
+					cluster_xp = xp;
+					cluster_yp = yp;
+					runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+				}
 
-			FcPatternDestroy(fcpattern);
-			FcCharSetDestroy(fccharset);
+				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;
+					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 = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
+					specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
+					cluster_xp += shaped.positions[code_idx].x_advance / 64.;
+					cluster_yp += shaped.positions[code_idx].y_advance / 64.;
+					numspecs++;
+				} else {
+					/* If it's not found, try to fetch it through the font cache. */
+					rune = glyphs[start + idx].u;
+					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;
+					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;
+			}
 		}
-
-		specs[numspecs].font = frc[f].font;
-		specs[numspecs].glyph = glyphidx;
-		specs[numspecs].x = (short)xp;
-		specs[numspecs].y = (short)yp;
-		xp += runewidth;
-		numspecs++;
 	}
 
+	hbcleanup(&shaped);
 	return numspecs;
 }
 
@@ -1534,14 +1587,17 @@ xdrawglyph(Glyph g, int x, int y)
 }
 
 void
-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+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;
-	xdrawglyph(og, ox, oy);
+
+	/* 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))
 		return;
@@ -1714,18 +1770,16 @@ xdrawline(Line line, int x1, int y1, int x2)
 	Glyph base, new;
 	XftGlyphFontSpec *specs = xw.specbuf;
 
-	numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
 	i = ox = 0;
-	for (x = x1; x < x2 && i < numspecs; x++) {
+	for (x = x1; x < x2; 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);
-			specs += i;
-			numspecs -= i;
+		if ((i > 0) && ATTRCMP(base, new)) {
+			numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
+			xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
 			i = 0;
 		}
 		if (i == 0) {
@@ -1734,8 +1788,10 @@ xdrawline(Line line, int x1, int y1, int x2)
 		}
 		i++;
 	}
-	if (i > 0)
-		xdrawglyphfontspecs(specs, base, i, ox, y1);
+	if (i > 0) {
+		numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
+		xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
+	}
 }
 
 void

From 6d3746db8a0a8c0e51ea5fae1ad2719ddbd941fc Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:41:51 +0100
Subject: [PATCH 08/13] selection colors patch

---
 config.def.h                                  |  7 +++
 ... st-selectioncolors-20231213-9846a56.diff} | 48 ++++++-------------
 st.h                                          |  1 +
 x.c                                           | 30 +++++-------
 4 files changed, 35 insertions(+), 51 deletions(-)
 rename patches/{st-selectioncolors-0.8.4.diff => st-selectioncolors-20231213-9846a56.diff} (60%)

diff --git a/config.def.h b/config.def.h
index 9ad34b5..21dbbfd 100644
--- a/config.def.h
+++ b/config.def.h
@@ -134,6 +134,7 @@ static const char *colorname[] = {
 	"#555555",
 	"gray90", /* default foreground colour */
 	"black", /* default background colour */
+	"#2e3440",
 };
 
 
@@ -145,6 +146,12 @@ unsigned int defaultfg = 258;
 unsigned int defaultbg = 259;
 unsigned int defaultcs = 256;
 static unsigned int defaultrcs = 257;
+static unsigned int defaultrcs = 256;
+unsigned int selectionbg = 260;
+unsigned int selectionfg = 7;
+/* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
+/* Else if 1 keep original foreground-color of each cell => more colors :) */
+static int ignoreselfg = 1;
 
 /*
  * 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
diff --git a/patches/st-selectioncolors-0.8.4.diff b/patches/st-selectioncolors-20231213-9846a56.diff
similarity index 60%
rename from patches/st-selectioncolors-0.8.4.diff
rename to patches/st-selectioncolors-20231213-9846a56.diff
index 4a8ef01..6b60ef0 100644
--- a/patches/st-selectioncolors-0.8.4.diff
+++ b/patches/st-selectioncolors-20231213-9846a56.diff
@@ -1,44 +1,26 @@
-From 0750c0b8c91579ec871c70c8990527bebc86a56e Mon Sep 17 00:00:00 2001
-From: Ashish Kumar Yadav <ashishkumar.yadav@students.iiserpune.ac.in>
-Date: Sat, 31 Jul 2021 22:21:11 +0530
-Subject: [PATCH] Add settings for selection-colors
-
-This patch adds the two color-settings *selectionfg* and *selectionbg* to
-config.def.h. Those define the fore- and background colors which are used when
-text on the screen is selected with the mouse. This removes the default
-behaviour which would simply reverse the colors.
-
-Additionally, a third setting *ingnoreselfg* exists. If true then the setting
-*selectionfg* is ignored and the original foreground-colors of each cell are
-not changed during selection. Basically only the background-color would change.
-This might be more visually appealing to some folks.
----
- config.def.h | 11 ++++++++---
- st.h         |  1 +
- x.c          | 30 +++++++++++++-----------------
- 3 files changed, 22 insertions(+), 20 deletions(-)
-
 diff --git a/config.def.h b/config.def.h
-index 6f05dce..3679af8 100644
+index 91ab8ca..a382605 100644
 --- a/config.def.h
 +++ b/config.def.h
-@@ -119,18 +119,23 @@ static const char *colorname[] = {
+@@ -119,7 +119,7 @@ static const char *colorname[] = {
  
  	/* more colors can be added after 255 to use with DefaultXX */
  	"#cccccc",
 -	"#555555",
 +	"#2e3440",
+ 	"gray90", /* default foreground colour */
+ 	"black", /* default background colour */
  };
- 
+@@ -127,12 +127,17 @@ static const char *colorname[] = {
  
  /*
   * Default colors (colorname index)
 - * foreground, background, cursor, reverse cursor
 + * foreground, background, cursor, reverse cursor, selection
   */
- unsigned int defaultfg = 7;
- unsigned int defaultbg = 0;
- static unsigned int defaultcs = 256;
+ unsigned int defaultfg = 258;
+ unsigned int defaultbg = 259;
+ unsigned int defaultcs = 256;
 -static unsigned int defaultrcs = 257;
 +static unsigned int defaultrcs = 256;
 +unsigned int selectionbg = 257;
@@ -50,7 +32,7 @@ index 6f05dce..3679af8 100644
  /*
   * Default shape of cursor
 diff --git a/st.h b/st.h
-index 3d351b6..e1a28c3 100644
+index fd3b0d8..1d56e54 100644
 --- a/st.h
 +++ b/st.h
 @@ -33,6 +33,7 @@ enum glyph_attribute {
@@ -62,10 +44,10 @@ index 3d351b6..e1a28c3 100644
  };
  
 diff --git a/x.c b/x.c
-index 210f184..7a6c70e 100644
+index b36fb8c..bba8be5 100644
 --- a/x.c
 +++ b/x.c
-@@ -1425,6 +1425,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+@@ -1457,6 +1457,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
  		bg = temp;
  	}
  
@@ -78,7 +60,7 @@ index 210f184..7a6c70e 100644
  	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
  		fg = bg;
  
-@@ -1491,7 +1497,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+@@ -1523,7 +1529,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
  
  	/* remove the old cursor */
  	if (selected(ox, oy))
@@ -87,7 +69,7 @@ index 210f184..7a6c70e 100644
  	xdrawglyph(og, ox, oy);
  
  	if (IS_SET(MODE_HIDE))
-@@ -1504,23 +1510,13 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+@@ -1536,23 +1542,13 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
  
  	if (IS_SET(MODE_REVERSE)) {
  		g.mode |= ATTR_REVERSE;
@@ -116,7 +98,7 @@ index 210f184..7a6c70e 100644
  	}
  
  	/* draw the new one */
-@@ -1612,7 +1608,7 @@ xdrawline(Line line, int x1, int y1, int x2)
+@@ -1659,7 +1655,7 @@ xdrawline(Line line, int x1, int y1, int x2)
  		if (new.mode == ATTR_WDUMMY)
  			continue;
  		if (selected(x, y1))
@@ -125,6 +107,4 @@ index 210f184..7a6c70e 100644
  		if (i > 0 && ATTRCMP(base, new)) {
  			xdrawglyphfontspecs(specs, base, i, ox, y1);
  			specs += i;
--- 
-2.32.0
 
diff --git a/st.h b/st.h
index ae41368..d0e6b73 100644
--- a/st.h
+++ b/st.h
@@ -35,6 +35,7 @@ enum glyph_attribute {
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
 	ATTR_BOXDRAW    = 1 << 11,
+	ATTR_SELECTED   = 1 << 11,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
 };
 
diff --git a/x.c b/x.c
index 0915226..f8c3c0c 100644
--- a/x.c
+++ b/x.c
@@ -1523,6 +1523,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 		bg = temp;
 	}
 
+	if (base.mode & ATTR_SELECTED) {
+		bg = &dc.col[selectionbg];
+		if (!ignoreselfg)
+			fg = &dc.col[selectionfg];
+	}
+
 	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
 		fg = bg;
 
@@ -1593,7 +1599,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le
 
 	/* remove the old cursor */
 	if (selected(ox, oy))
-		og.mode ^= ATTR_REVERSE;
+		og.mode |= ATTR_SELECTED;
 
 	/* Redraw the line where cursor was previously.
 	 * It will restore the ligatures broken by the cursor. */
@@ -1609,23 +1615,13 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le
 
 	if (IS_SET(MODE_REVERSE)) {
 		g.mode |= ATTR_REVERSE;
+		g.fg = defaultcs;
 		g.bg = defaultfg;
-		if (selected(cx, cy)) {
-			drawcol = dc.col[defaultcs];
-			g.fg = defaultrcs;
-		} else {
-			drawcol = dc.col[defaultrcs];
-			g.fg = defaultcs;
-		}
+		drawcol = dc.col[defaultrcs];
 	} else {
-		if (selected(cx, cy)) {
-			g.fg = defaultfg;
-			g.bg = defaultrcs;
-		} else {
-			g.fg = defaultbg;
-			g.bg = defaultcs;
-		}
-		drawcol = dc.col[g.bg];
+		g.fg = defaultbg;
+		g.bg = defaultcs;
+		drawcol = dc.col[defaultcs];
 	}
 
 	/* draw the new one */
@@ -1776,7 +1772,7 @@ xdrawline(Line line, int x1, int y1, int x2)
 		if (new.mode == ATTR_WDUMMY)
 			continue;
 		if (selected(x, y1))
-			new.mode ^= ATTR_REVERSE;
+			new.mode |= ATTR_SELECTED;
 		if ((i > 0) && ATTRCMP(base, new)) {
 			numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
 			xdrawglyphfontspecs(specs, base, numspecs, ox, y1);

From a4e7ec7a26225ae2a41cda2b595a4088f8cc5610 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:48:26 +0100
Subject: [PATCH 09/13] undercurl patch

---
 config.def.h                                  |  24 ++
 ...iff => st-undercurl-20231213-9846a56.diff} |  31 +-
 st.c                                          |  51 ++-
 st.h                                          |   3 +
 st.info                                       |   1 +
 x.c                                           | 406 +++++++++++++++++-
 6 files changed, 498 insertions(+), 18 deletions(-)
 rename patches/{st-undercurl-0.8.4-20210822.diff => st-undercurl-20231213-9846a56.diff} (95%)

diff --git a/config.def.h b/config.def.h
index 21dbbfd..a204470 100644
--- a/config.def.h
+++ b/config.def.h
@@ -498,3 +498,27 @@ static char ascii_printable[] =
 	" !\"#$%&'()*+,-./0123456789:;<=>?"
 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
 	"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/**
+ * 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/patches/st-undercurl-0.8.4-20210822.diff b/patches/st-undercurl-20231213-9846a56.diff
similarity index 95%
rename from patches/st-undercurl-0.8.4-20210822.diff
rename to patches/st-undercurl-20231213-9846a56.diff
index fab2ddc..7016e4d 100644
--- a/patches/st-undercurl-0.8.4-20210822.diff
+++ b/patches/st-undercurl-20231213-9846a56.diff
@@ -1,8 +1,8 @@
 diff --git a/config.def.h b/config.def.h
-index 6f05dce..7ae1b92 100644
+index 91ab8ca..b959ad6 100644
 --- a/config.def.h
 +++ b/config.def.h
-@@ -470,3 +470,27 @@ static char ascii_printable[] =
+@@ -472,3 +472,27 @@ static char ascii_printable[] =
  	" !\"#$%&'()*+,-./0123456789:;<=>?"
  	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
  	"`abcdefghijklmnopqrstuvwxyz{|}~";
@@ -31,7 +31,7 @@ index 6f05dce..7ae1b92 100644
 +// Active style
 +#define UNDERCURL_STYLE UNDERCURL_SPIKY
 diff --git a/st.c b/st.c
-index 76b7e0d..542ab3a 100644
+index d6478f5..aedf938 100644
 --- a/st.c
 +++ b/st.c
 @@ -33,6 +33,7 @@
@@ -57,8 +57,8 @@ index 76b7e0d..542ab3a 100644
 +static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
  static void csiparse(void);
  static void csireset(void);
- static int eschandle(uchar);
-@@ -1131,6 +1134,28 @@ tnewline(int first_col)
+ static void osc_color_response(int, int, int);
+@@ -1127,6 +1130,28 @@ tnewline(int first_col)
  	tmoveto(first_col ? 0 : term.c.x, y);
  }
  
@@ -87,7 +87,7 @@ index 76b7e0d..542ab3a 100644
  void
  csiparse(void)
  {
-@@ -1153,6 +1178,7 @@ csiparse(void)
+@@ -1149,6 +1174,7 @@ csiparse(void)
  			v = -1;
  		csiescseq.arg[csiescseq.narg++] = v;
  		p = np;
@@ -95,7 +95,7 @@ index 76b7e0d..542ab3a 100644
  		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
  			break;
  		p++;
-@@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l)
+@@ -1365,6 +1391,10 @@ tsetattr(const int *attr, int l)
  				ATTR_STRUCK     );
  			term.c.attr.fg = defaultfg;
  			term.c.attr.bg = defaultbg;
@@ -106,7 +106,7 @@ index 76b7e0d..542ab3a 100644
  			break;
  		case 1:
  			term.c.attr.mode |= ATTR_BOLD;
-@@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l)
+@@ -1376,7 +1406,14 @@ tsetattr(const int *attr, int l)
  			term.c.attr.mode |= ATTR_ITALIC;
  			break;
  		case 4:
@@ -122,7 +122,7 @@ index 76b7e0d..542ab3a 100644
  			break;
  		case 5: /* slow blink */
  			/* FALLTHROUGH */
-@@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l)
+@@ -1427,6 +1464,18 @@ tsetattr(const int *attr, int l)
  		case 49:
  			term.c.attr.bg = defaultbg;
  			break;
@@ -142,7 +142,7 @@ index 76b7e0d..542ab3a 100644
  			if (BETWEEN(attr[i], 30, 37)) {
  				term.c.attr.fg = attr[i] - 30;
 diff --git a/st.h b/st.h
-index 3d351b6..95bdcbd 100644
+index fd3b0d8..5d398b5 100644
 --- a/st.h
 +++ b/st.h
 @@ -34,6 +34,7 @@ enum glyph_attribute {
@@ -163,7 +163,7 @@ index 3d351b6..95bdcbd 100644
  
  typedef Glyph *Line;
 diff --git a/st.info b/st.info
-index 8201ad6..659878c 100644
+index efab2cf..23b1875 100644
 --- a/st.info
 +++ b/st.info
 @@ -1,4 +1,5 @@
@@ -173,7 +173,7 @@ index 8201ad6..659878c 100644
  	am,
  	bce,
 diff --git a/x.c b/x.c
-index 210f184..3a0e79e 100644
+index b36fb8c..2f036d1 100644
 --- a/x.c
 +++ b/x.c
 @@ -45,6 +45,14 @@ typedef struct {
@@ -191,7 +191,7 @@ index 210f184..3a0e79e 100644
  /* X modifiers */
  #define XK_ANY_MOD    UINT_MAX
  #define XK_NO_MOD     0
-@@ -1339,6 +1347,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
+@@ -1371,6 +1379,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
  	return numspecs;
  }
  
@@ -243,11 +243,11 @@ index 210f184..3a0e79e 100644
  void
  xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
  {
-@@ -1461,8 +1514,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+@@ -1493,8 +1546,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
  
  	/* Render underline and strikethrough. */
  	if (base.mode & ATTR_UNDERLINE) {
--		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
+-		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
 -				width, 1);
 +		// Underline Color
 +		const int widthThreshold  = 28; // +1 width every widthThreshold px of font
@@ -603,3 +603,4 @@ index 210f184..3a0e79e 100644
  	}
  
  	if (base.mode & ATTR_STRUCK) {
+
diff --git a/st.c b/st.c
index c2e0880..ff3d0d6 100644
--- a/st.c
+++ b/st.c
@@ -33,6 +33,7 @@
 #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
 
@@ -139,6 +140,7 @@ typedef struct {
 	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 */
@@ -159,6 +161,7 @@ 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 osc_color_response(int, int, int);
@@ -1127,6 +1130,28 @@ tnewline(int first_col)
 	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)
 {
@@ -1149,6 +1174,7 @@ csiparse(void)
 			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++;
@@ -1368,6 +1394,10 @@ tsetattr(const int *attr, int l)
 				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;
@@ -1379,7 +1409,14 @@ tsetattr(const int *attr, int l)
 			term.c.attr.mode |= ATTR_ITALIC;
 			break;
 		case 4:
-			term.c.attr.mode |= ATTR_UNDERLINE;
+			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 */
@@ -1430,6 +1467,18 @@ tsetattr(const int *attr, int l)
 		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;
diff --git a/st.h b/st.h
index d0e6b73..a420060 100644
--- a/st.h
+++ b/st.h
@@ -37,6 +37,7 @@ enum glyph_attribute {
 	ATTR_BOXDRAW    = 1 << 11,
 	ATTR_SELECTED   = 1 << 11,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+	ATTR_DIRTYUNDERLINE = 1 << 15,
 };
 
 enum selection_mode {
@@ -68,6 +69,8 @@ typedef struct {
 	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;
diff --git a/st.info b/st.info
index 24dfc7d..caef745 100644
--- a/st.info
+++ b/st.info
@@ -1,4 +1,5 @@
 st-mono| simpleterm monocolor,
+	Su,
 	acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
 	am,
 	bce,
diff --git a/x.c b/x.c
index f8c3c0c..e0bd742 100644
--- a/x.c
+++ b/x.c
@@ -46,6 +46,14 @@ typedef struct {
 	signed char appcursor; /* application cursor */
 } Key;
 
+/* 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
@@ -1441,6 +1449,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 	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)
 {
@@ -1569,8 +1622,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
 	/* Render underline and strikethrough. */
 	if (base.mode & ATTR_UNDERLINE) {
-		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
-				width, 1);
+		// 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) {
+			//XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
+			XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
+				winy + dc.font.ascent + 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) {

From 8d347bfd3cdd3c08c5ca112db272b937c1ba450e Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:49:41 +0100
Subject: [PATCH 10/13] workingdir patch

---
 st.1 |  8 ++++++++
 x.c  | 13 +++++++++----
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/st.1 b/st.1
index 39120b4..b82beb3 100644
--- a/st.1
+++ b/st.1
@@ -6,6 +6,8 @@ st \- simple terminal
 .RB [ \-aiv ]
 .RB [ \-c
 .IR class ]
+.RB [ \-d
+.IR path ]
 .RB [ \-f
 .IR font ]
 .RB [ \-g
@@ -30,6 +32,8 @@ st \- simple terminal
 .RB [ \-aiv ]
 .RB [ \-c
 .IR class ]
+.RB [ \-d
+.IR path ]
 .RB [ \-f
 .IR font ]
 .RB [ \-g
@@ -58,6 +62,10 @@ disable alternate screens in terminal
 .BI \-c " class"
 defines the window class (default $TERM).
 .TP
+.BI \-d " path"
+changes the working directory to
+.IR path .
+.TP
 .BI \-f " font"
 defines the
 .I font
diff --git a/x.c b/x.c
index e0bd742..92b8f92 100644
--- a/x.c
+++ b/x.c
@@ -267,6 +267,7 @@ 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 uint buttons; /* bit field of pressed buttons */
 static int cursorblinks = 0;
@@ -2540,12 +2541,12 @@ run(void)
 void
 usage(void)
 {
-	die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
-	    " [-n name] [-o file]\n"
+	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] [-f font] [-g geometry]"
-	    " [-n name] [-o file]\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);
 }
@@ -2597,6 +2598,9 @@ main(int argc, char *argv[])
 	case 'v':
 		die("%s " VERSION "\n", argv0);
 		break;
+	case 'd':
+		opt_dir = EARGF(usage());
+		break;
 	default:
 		usage();
 	} ARGEND;
@@ -2616,6 +2620,7 @@ run:
 	xinit(cols, rows);
 	xsetenv();
 	selinit();
+	chdir(opt_dir);
 	run();
 
 	return 0;

From a876a0d419b36c67748e98cbdd18039e275c8bd9 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 11:49:53 +0100
Subject: [PATCH 11/13] cleanup

---
 .clang-format |   8 -
 .clang-tidy   |  30 ---
 .clangd       |   3 -
 .gitignore    |   5 +
 config.def.h  |   1 -
 config.h      | 523 ++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 528 insertions(+), 42 deletions(-)
 delete mode 100644 .clang-format
 delete mode 100644 .clang-tidy
 delete mode 100644 .clangd
 create mode 100644 config.h

diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index 299675f..0000000
--- a/.clang-format
+++ /dev/null
@@ -1,8 +0,0 @@
-BasedOnStyle: Google
-IndentWidth: 4
-InsertBraces: true
-ColumnLimit: 79
-AlignConsecutiveMacros: Consecutive
-AllowShortFunctionsOnASingleLine: None
-AllowShortLoopsOnASingleLine: false
-AllowShortIfStatementsOnASingleLine: Never
diff --git a/.clang-tidy b/.clang-tidy
deleted file mode 100644
index 17ce268..0000000
--- a/.clang-tidy
+++ /dev/null
@@ -1,30 +0,0 @@
-Checks: |
-  -*,
-  abseil-*,
-  bugprone-*,
-  clang-analyzer-*,
-  misc-*,
-  modernize-*,
-  performance-*,
-  portability-*,
-  readability-*,
-  llvm-*,
-  -bugprone-easily-swappable-parameters,
-  -readability-avoid-const-params-in-decls,
-  -readability-identifier-length
-
-CheckOptions:
-  - key: readability-inconsistent-declaration-parameter-name.Strict
-    value: true
-  - key: readability-identifier-naming.StructCase
-    value: lower_case
-  - key: readability-identifier-naming.FunctionCase
-    value: lower_case
-  - key: readability-identifier-naming.VariableCase
-    value: lower_case
-  - key: readability-identifier-naming.EnumConstantCase
-    value: UPPER_CASE
-  - key: readability-identifier-naming.MacroDefinitionCase
-    value: UPPER_CASE
-  - key: readability-function-cognitive-complexity.Threshold
-    value: 15
diff --git a/.clangd b/.clangd
deleted file mode 100644
index f8cd025..0000000
--- a/.clangd
+++ /dev/null
@@ -1,3 +0,0 @@
-Diagnostics:
-  UnusedIncludes: Strict
-  MissingIncludes: Strict
diff --git a/.gitignore b/.gitignore
index 9bf3746..9e35ca2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
 compile_commands.json
+st.o
+boxdraw.o
+hb.o
+st
+x.o
diff --git a/config.def.h b/config.def.h
index a204470..853ef31 100644
--- a/config.def.h
+++ b/config.def.h
@@ -146,7 +146,6 @@ unsigned int defaultfg = 258;
 unsigned int defaultbg = 259;
 unsigned int defaultcs = 256;
 static unsigned int defaultrcs = 257;
-static unsigned int defaultrcs = 256;
 unsigned int selectionbg = 260;
 unsigned int selectionfg = 7;
 /* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..853ef31
--- /dev/null
+++ b/config.h
@@ -0,0 +1,523 @@
+/* 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";
+static int borderpx = 2;
+
+/*
+ * 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[?6c";
+
+/* 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;
+
+/*
+ * 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;
+
+/* 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 */
+	"#cccccc",
+	"#555555",
+	"gray90", /* default foreground colour */
+	"black", /* default background colour */
+	"#2e3440",
+};
+
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
+ */
+unsigned int defaultfg = 258;
+unsigned int defaultbg = 259;
+unsigned int defaultcs = 256;
+static unsigned int defaultrcs = 257;
+unsigned int selectionbg = 260;
+unsigned int selectionfg = 7;
+/* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
+/* Else if 1 keep original foreground-color of each cell => more colors :) */
+static int ignoreselfg = 1;
+
+/*
+ * 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 ("☃") */
+
+/*
+ * Default columns and rows numbers
+ */
+
+static unsigned int cols = 80;
+static unsigned int rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static unsigned int mouseshape = XC_xterm;
+static unsigned int mousefg = 7;
+static unsigned int mousebg = 0;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static unsigned int defaultattr = 11;
+
+/*
+ * 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 */
+	{ XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
+	{ ShiftMask,            Button4, ttysend,        {.s = "\033[5;2~"} },
+	{ XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"} },
+	{ ShiftMask,            Button5, ttysend,        {.s = "\033[6;2~"} },
+	{ XK_ANY_MOD,           Button5, ttysend,        {.s = "\005"} },
+};
+
+/* Internal keyboard shortcuts. */
+#define MODKEY Mod1Mask
+#define TERMMOD (ControlMask|ShiftMask)
+
+static Shortcut shortcuts[] = {
+	/* mask                 keysym          function        argument */
+	{ 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_Y,           selpaste,       {.i =  0} },
+	{ ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
+	{ TERMMOD,              XK_Num_Lock,    numlock,        {.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.
+ */
+
+/*
+ * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF)
+ * to be mapped below, add them to this array.
+ */
+static KeySym mappedkeys[] = { -1 };
+
+/*
+ * 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;
+
+/*
+ * 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_Home,       XK_ANY_MOD,     "\033[H",        0,   -1},
+	{ XK_KP_Home,       XK_ANY_MOD,     "\033[1~",       0,   +1},
+	{ XK_KP_Up,         XK_ANY_MOD,     "\033Ox",       +1,    0},
+	{ XK_KP_Up,         XK_ANY_MOD,     "\033[A",        0,   -1},
+	{ XK_KP_Up,         XK_ANY_MOD,     "\033OA",        0,   +1},
+	{ XK_KP_Down,       XK_ANY_MOD,     "\033Or",       +1,    0},
+	{ XK_KP_Down,       XK_ANY_MOD,     "\033[B",        0,   -1},
+	{ XK_KP_Down,       XK_ANY_MOD,     "\033OB",        0,   +1},
+	{ XK_KP_Left,       XK_ANY_MOD,     "\033Ot",       +1,    0},
+	{ XK_KP_Left,       XK_ANY_MOD,     "\033[D",        0,   -1},
+	{ XK_KP_Left,       XK_ANY_MOD,     "\033OD",        0,   +1},
+	{ XK_KP_Right,      XK_ANY_MOD,     "\033Ov",       +1,    0},
+	{ XK_KP_Right,      XK_ANY_MOD,     "\033[C",        0,   -1},
+	{ XK_KP_Right,      XK_ANY_MOD,     "\033OC",        0,   +1},
+	{ XK_KP_Prior,      ShiftMask,      "\033[5;2~",     0,    0},
+	{ XK_KP_Prior,      XK_ANY_MOD,     "\033[5~",       0,    0},
+	{ XK_KP_Begin,      XK_ANY_MOD,     "\033[E",        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_End,        XK_ANY_MOD,     "\033[4~",       0,    0},
+	{ XK_KP_Next,       ShiftMask,      "\033[6;2~",     0,    0},
+	{ XK_KP_Next,       XK_ANY_MOD,     "\033[6~",       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_Insert,     XK_ANY_MOD,     "\033[4h",      -1,    0},
+	{ XK_KP_Insert,     XK_ANY_MOD,     "\033[2~",      +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_KP_Delete,     XK_ANY_MOD,     "\033[P",       -1,    0},
+	{ XK_KP_Delete,     XK_ANY_MOD,     "\033[3~",      +1,    0},
+	{ XK_KP_Multiply,   XK_ANY_MOD,     "\033Oj",       +2,    0},
+	{ XK_KP_Add,        XK_ANY_MOD,     "\033Ok",       +2,    0},
+	{ XK_KP_Enter,      XK_ANY_MOD,     "\033OM",       +2,    0},
+	{ XK_KP_Enter,      XK_ANY_MOD,     "\r",           -1,    0},
+	{ XK_KP_Subtract,   XK_ANY_MOD,     "\033Om",       +2,    0},
+	{ XK_KP_Decimal,    XK_ANY_MOD,     "\033On",       +2,    0},
+	{ XK_KP_Divide,     XK_ANY_MOD,     "\033Oo",       +2,    0},
+	{ XK_KP_0,          XK_ANY_MOD,     "\033Op",       +2,    0},
+	{ XK_KP_1,          XK_ANY_MOD,     "\033Oq",       +2,    0},
+	{ XK_KP_2,          XK_ANY_MOD,     "\033Or",       +2,    0},
+	{ XK_KP_3,          XK_ANY_MOD,     "\033Os",       +2,    0},
+	{ XK_KP_4,          XK_ANY_MOD,     "\033Ot",       +2,    0},
+	{ XK_KP_5,          XK_ANY_MOD,     "\033Ou",       +2,    0},
+	{ XK_KP_6,          XK_ANY_MOD,     "\033Ov",       +2,    0},
+	{ XK_KP_7,          XK_ANY_MOD,     "\033Ow",       +2,    0},
+	{ XK_KP_8,          XK_ANY_MOD,     "\033Ox",       +2,    0},
+	{ XK_KP_9,          XK_ANY_MOD,     "\033Oy",       +2,    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_ANY_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_Insert,        XK_ANY_MOD,     "\033[4h",      -1,    0},
+	{ XK_Insert,        XK_ANY_MOD,     "\033[2~",      +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_Delete,        XK_ANY_MOD,     "\033[P",       -1,    0},
+	{ XK_Delete,        XK_ANY_MOD,     "\033[3~",      +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_Home,          XK_ANY_MOD,     "\033[H",        0,   -1},
+	{ XK_Home,          XK_ANY_MOD,     "\033[1~",       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_End,           XK_ANY_MOD,     "\033[4~",       0,    0},
+	{ XK_Prior,         ControlMask,    "\033[5;5~",     0,    0},
+	{ XK_Prior,         ShiftMask,      "\033[5;2~",     0,    0},
+	{ XK_Prior,         XK_ANY_MOD,     "\033[5~",       0,    0},
+	{ XK_Next,          ControlMask,    "\033[6;5~",     0,    0},
+	{ XK_Next,          ShiftMask,      "\033[6;2~",     0,    0},
+	{ XK_Next,          XK_ANY_MOD,     "\033[6~",       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},
+};
+
+/*
+ * 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{|}~";
+
+/**
+ * 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

From bb54a7174b132004881907f54f74e74b32f5f974 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 12:52:01 +0100
Subject: [PATCH 12/13] set config

---
 config.h | 133 ++++++++++++++++++++++---------------------------------
 1 file changed, 54 insertions(+), 79 deletions(-)

diff --git a/config.h b/config.h
index 853ef31..cf9799c 100644
--- a/config.h
+++ b/config.h
@@ -1,13 +1,16 @@
+// vim:set ft=c:
 /* 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";
+static char *font = "FiraCode Nerd Font Mono:pixelsize=14";
 static int borderpx = 2;
 
+/* Underline curl style
+ * CURLY 0
+ * SPIKY 1
+ * CAPPED 2
+ */
+#define UNDERCURL_STYLE 2
+
 /*
  * What program is execed by st depends of these precedence rules:
  * 1: program passed with -e
@@ -53,8 +56,8 @@ int allowwindowops = 0;
  * 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;
+static double minlatency = 3;
+static double maxlatency = 25;
 
 /*
  * blinking timeout (set to 0 to disable blinking) for the terminal blinking
@@ -73,7 +76,7 @@ static unsigned int cursorthickness = 2;
  *    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 = 1;
 const int boxdraw_bold = 0;
 
 /* braille (U28XX):  1: render as adjacent "pixels",  0: use font */
@@ -108,62 +111,58 @@ unsigned int tabspaces = 8;
 /* Terminal colors (16 first used in escape sequence) */
 static const char *colorname[] = {
 	/* 8 normal colors */
-	"black",
-	"red3",
-	"green3",
-	"yellow3",
-	"blue2",
-	"magenta3",
-	"cyan3",
-	"gray90",
+	"#15161E",
+	"#f7768e",
+	"#9ece6a",
+	"#e0af68",
+	"#7aa2f7",
+	"#bb9af7",
+	"#7dcfff",
+	"#a9b1d6",
 
 	/* 8 bright colors */
-	"gray50",
-	"red",
-	"green",
-	"yellow",
-	"#5c5cff",
-	"magenta",
-	"cyan",
-	"white",
+	"#414868",
+	"#f7768e",
+	"#9ece6a",
+	"#e0af68",
+	"#7aa2f7",
+	"#bb9af7",
+	"#7dcfff",
+	"#c0caf5",
 
 	[255] = 0,
 
-	/* more colors can be added after 255 to use with DefaultXX */
-	"#cccccc",
-	"#555555",
-	"gray90", /* default foreground colour */
-	"black", /* default background colour */
-	"#2e3440",
+    // indexes beyond 255 are used for internal colors
+	"#c0caf5", /* default cursor colour */
+	"#1a1b26", /* default inverted cursor colour */
+	"#c0caf5", /* default foreground colour */
+	"#1a1b26", /* default background colour */
+    "#c0caf5", /* default selection foreground colour */
+    "#33467C", /* default selection background colour */
 };
 
-
-/*
- * Default colors (colorname index)
- * foreground, background, cursor, reverse cursor
- */
-unsigned int defaultfg = 258;
-unsigned int defaultbg = 259;
 unsigned int defaultcs = 256;
 static unsigned int defaultrcs = 257;
-unsigned int selectionbg = 260;
-unsigned int selectionfg = 7;
+unsigned int defaultfg = 258;
+unsigned int defaultbg = 259;
+unsigned int selectionfg = 260;
+unsigned int selectionbg = 261;
 /* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
 /* Else if 1 keep original foreground-color of each cell => more colors :) */
 static int ignoreselfg = 1;
 
 /*
- * 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
++ * 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 ("☃") */
@@ -218,8 +217,8 @@ static Shortcut shortcuts[] = {
 	{ 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_Prior,       zoom,           {.f = +5} },
+	{ TERMMOD,              XK_Next,        zoom,           {.f = -5} },
 	{ TERMMOD,              XK_Home,        zoomreset,      {.f =  0} },
 	{ TERMMOD,              XK_C,           clipcopy,       {.i =  0} },
 	{ TERMMOD,              XK_V,           clippaste,      {.i =  0} },
@@ -303,7 +302,7 @@ static Key key[] = {
 	{ 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_KP_Delete,     XK_ANY_MOD,     "\033[P",       -1,    0},
+	{ XK_KP_Delete,     XK_ANY_MOD,     "\033[3~",      -1,    0},
 	{ XK_KP_Delete,     XK_ANY_MOD,     "\033[3~",      +1,    0},
 	{ XK_KP_Multiply,   XK_ANY_MOD,     "\033Oj",       +2,    0},
 	{ XK_KP_Add,        XK_ANY_MOD,     "\033Ok",       +2,    0},
@@ -371,7 +370,7 @@ static Key key[] = {
 	{ XK_Delete,        ControlMask,    "\033[3;5~",    +1,    0},
 	{ XK_Delete,        ShiftMask,      "\033[2K",      -1,    0},
 	{ XK_Delete,        ShiftMask,      "\033[3;2~",    +1,    0},
-	{ XK_Delete,        XK_ANY_MOD,     "\033[P",       -1,    0},
+	{ XK_Delete,        XK_ANY_MOD,     "\033[3~",      -1,    0},
 	{ XK_Delete,        XK_ANY_MOD,     "\033[3~",      +1,    0},
 	{ XK_BackSpace,     XK_NO_MOD,      "\177",          0,    0},
 	{ XK_BackSpace,     Mod1Mask,       "\033\177",      0,    0},
@@ -497,27 +496,3 @@ static char ascii_printable[] =
 	" !\"#$%&'()*+,-./0123456789:;<=>?"
 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
 	"`abcdefghijklmnopqrstuvwxyz{|}~";
-
-/**
- * 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

From 53174378c92c96bf84d81d8d95ed2a4f20ffdba5 Mon Sep 17 00:00:00 2001
From: Luca Bilke <bilke@tralios.de>
Date: Wed, 13 Dec 2023 13:04:06 +0100
Subject: [PATCH 13/13] fix boxdraw/selcolor bug

---
 st.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/st.h b/st.h
index a420060..8534870 100644
--- a/st.h
+++ b/st.h
@@ -35,7 +35,7 @@ enum glyph_attribute {
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
 	ATTR_BOXDRAW    = 1 << 11,
-	ATTR_SELECTED   = 1 << 11,
+	ATTR_SELECTED   = 1 << 12,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
 	ATTR_DIRTYUNDERLINE = 1 << 15,
 };