[hackers] [PATCH] Implement Kitty underline color sequences

From: Roberto E. Vargas Caballero <k0ga_AT_shike2.net>
Date: Tue, 30 Dec 2025 12:22:59 +0100

St was faking the set underline color sequence from the
Kitty [1] terminal. This patch implements correctly the
sequences SGR 58 and SGR 59 to enable or disable chaging
the color.

[1] https://sw.kovidgoyal.net/kitty/underlines/
---
 st.c    | 15 ++++++++++-----
 st.h    |  2 ++
 st.info |  2 ++
 x.c     | 20 +++++++++++++++++---
 4 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/st.c b/st.c
index e55e7b3..4fd230d 100644
--- a/st.c
+++ b/st.c
_AT_@ -1015,7 +1015,8 @@ treset(void)
 	term.c = (TCursor){{
 		.mode = ATTR_NULL,
 		.fg = defaultfg,
-		.bg = defaultbg
+		.bg = defaultbg,
+		.un = defaultfg,
 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
 
 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
_AT_@ -1362,6 +1363,7 @@ tsetattr(const int *attr, int l)
 				ATTR_FAINT      |
 				ATTR_ITALIC     |
 				ATTR_UNDERLINE  |
+			        ATTR_UNDERCOLOR |
 				ATTR_BLINK      |
 				ATTR_REVERSE    |
 				ATTR_INVISIBLE  |
_AT_@ -1431,10 +1433,13 @@ tsetattr(const int *attr, int l)
 			term.c.attr.bg = defaultbg;
 			break;
 		case 58:
-			/* This starts a sequence to change the color of
-			 * "underline" pixels. We don't support that and
-			 * instead eat up a following "5;n" or "2;r;g;b". */
-			tdefcolor(attr, &i, l);
+			if ((idx = tdefcolor(attr, &i, l)) >= 0) {
+				term.c.attr.mode |= ATTR_UNDERCOLOR;
+				term.c.attr.un = idx;
+			}
+			break;
+		case 59:
+			term.c.attr.mode &= ~ATTR_UNDERCOLOR;
 			break;
 		default:
 			if (BETWEEN(attr[i], 30, 37)) {
diff --git a/st.h b/st.h
index fd3b0d8..4cc16e3 100644
--- a/st.h
+++ b/st.h
_AT_@ -33,6 +33,7 @@ enum glyph_attribute {
 	ATTR_WRAP       = 1 << 8,
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
+	ATTR_UNDERCOLOR = 1 << 11,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
 };
 
_AT_@ -65,6 +66,7 @@ typedef struct {
 	ushort mode;      /* attribute flags */
 	uint32_t fg;      /* foreground  */
 	uint32_t bg;      /* background  */
+	uint32_t un;      /* underline */
 } Glyph;
 
 typedef Glyph *Line;
diff --git a/st.info b/st.info
index efab2cf..05a05f6 100644
--- a/st.info
+++ b/st.info
_AT_@ -195,6 +195,8 @@ st-mono| simpleterm monocolor,
 	Ms=\E]52;%p1%s;%p2%s\007,
 	Se=\E[2 q,
 	Ss=\E[%p1%d q,
+# kitty extensions
+	Su,
 
 st| simpleterm,
 	use=st-mono,
diff --git a/x.c b/x.c
index d73152b..d5b1dd3 100644
--- a/x.c
+++ b/x.c
_AT_@ -1380,8 +1380,8 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
 	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;
+	Color *un, *fg, *bg, *temp, revfg, revbg, trueun, truefg, truebg;
+	XRenderColor colun, colfg, colbg;
 	XRectangle r;
 
 	/* Fallback on color display for attributes not supported by the font */
_AT_@ -1466,6 +1466,20 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	if (base.mode & ATTR_INVISIBLE)
 		fg = bg;
 
+	un = fg;
+	if (base.mode & ATTR_UNDERCOLOR) {
+		if (IS_TRUECOL(base.un)) {
+			colun.alpha = 0xffff;
+			colun.red = TRUERED(base.un);
+			colun.green = TRUEGREEN(base.un);
+			colun.blue = TRUEBLUE(base.un);
+			XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colun, &trueun);
+			un = &trueun;
+		} else {
+			un = &dc.col[base.un];
+		}
+	}
+
 	/* Intelligent cleaning up of the borders. */
 	if (x == 0) {
 		xclear(0, (y == 0)? 0 : winy, borderpx,
_AT_@ -1496,7 +1510,7 @@ 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,
+		XftDrawRect(xw.draw, un, winx, winy + dc.font.ascent * chscale + 1,
 				width, 1);
 	}
 
-- 
2.46.1
Received on Tue Dec 30 2025 - 12:22:59 CET

This archive was generated by hypermail 2.3.0 : Tue Dec 30 2025 - 12:24:33 CET