[wiki] [sites] [st][patches][background_image] Fix minor portability and robustness issues || Matthias Schoth
commit ee8825185dc03e787434e7a00efd3fd6a78dad42
Author: Matthias Schoth <mschoth_at_gmail.com>
Date: Sun Nov 8 17:13:22 2026 +0100
[st][patches][background_image] Fix minor portability and robustness issues
- Honor reverse video when clearing the background
- Render correct colors on 16-bit and other TrueColor depths
- Decode farbfeld data endian-independently and reject unsupported visuals
- Validate _ST_BACKGROUND_IMAGE pixmaps before use and recover from error
- Prevent stbg from killing unrelated clients through a stale property
- Delay releasing the previous background after publishing the new one
- Bound image dimensions and validate file size before allocating
- Check XPutImage and XChangeProperty failures
diff --git a/st.suckless.org/patches/background_image/st-background-image-0.9.3.diff b/st.suckless.org/patches/background_image/st-background-image-0.9.3.diff
index 8f28de88..570d11d4 100644
--- a/st.suckless.org/patches/background_image/st-background-image-0.9.3.diff
+++ b/st.suckless.org/patches/background_image/st-background-image-0.9.3.diff
_at_@ -1,6 +1,6 @@
-From d910857f5b63dbfca4299a0daa7ce3155c880543 Mon Sep 17 00:00:00 2001
+From 58b00ef34d4f8c34070f1544537f64b69bb91aab Mon Sep 17 00:00:00 2001
From: Matthias Schoth <mschoth_at_gmail.com>
-Date: Sun, 8 Nov 2026 14:15:23 +0100
+Date: Sun, 8 Nov 2026 17:03:18 +0100
Subject: [PATCH] Add background image support via root window pixmap
Add a background image to st by tiling a server-side pixmap set as the
_at_@ -21,9 +21,9 @@ property. It is not built by default; compile separately with:
config.def.h | 6 ++
st.c | 2 +-
st.h | 1 +
- stbg.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++
- x.c | 147 ++++++++++++++++++++++++++++++++++++++----
- 5 files changed, 322 insertions(+), 13 deletions(-)
+ stbg.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ x.c | 204 ++++++++++++++++++++++++++++++++++++---
+ 5 files changed, 464 insertions(+), 13 deletions(-)
create mode 100644 stbg.c
diff --git a/config.def.h b/config.def.h
_at_@ -70,10 +70,10 @@ index fd3b0d8..a716a73 100644
int ttynew(const char *, char *, const char *, char **);
diff --git a/stbg.c b/stbg.c
new file mode 100644
-index 0000000..ed9c2fa
+index 0000000..b984aa1
--- /dev/null
+++ b/stbg.c
-_at_@ -0,0 +1,179 @@
+_at_@ -0,0 +1,264 @@
+/* See LICENSE file for copyright and license details. */
+/* Build: cc -o stbg stbg.c -lX11 */
+#include <errno.h>
_at_@ -83,13 +83,18 @@ index 0000000..ed9c2fa
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
++#include <sys/stat.h>
++#include <unistd.h>
+#include <arpa/inet.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
++#define MAX_PIXELS (1u << 27)
++
+static char *argv0;
-+static int xerror(Display *dpy, XErrorEvent *e) { (void)dpy; (void)e; return 0; }
++static int xerr;
++static int xerror(Display *dpy, XErrorEvent *e) { (void)dpy; xerr = e->error_code; return 0; }
+
+static void
+die(const char *errstr, ...)
_at_@ -132,12 +137,32 @@ index 0000000..ed9c2fa
+ return prev;
+}
+
++static int
++ispixmap(Display *dpy, Pixmap pm)
++{
++ XWindowAttributes wa;
++ Window root;
++ XErrorHandler oldh;
++ int x, y, ok;
++ unsigned int w, h, bw, depth;
++
++ oldh = XSetErrorHandler(xerror);
++ if (XGetWindowAttributes(dpy, pm, &wa)) {
++ XSetErrorHandler(oldh);
++ return 0;
++ }
++ ok = XGetGeometry(dpy, pm, &root, &x, &y, &w, &h, &bw, &depth);
++ XSync(dpy, False);
++ XSetErrorHandler(oldh);
++ return ok;
++}
++
+static void
+killprev(Display *dpy, Pixmap prev)
+{
+ XErrorHandler oldh;
+
-+ if (prev == None)
++ if (prev == None || !ispixmap(dpy, prev))
+ return;
+ oldh = XSetErrorHandler(xerror);
+ XKillClient(dpy, prev);
_at_@ -145,23 +170,45 @@ index 0000000..ed9c2fa
+ XSetErrorHandler(oldh);
+}
+
++static unsigned long
++channel(unsigned int v, unsigned long mask)
++{
++ unsigned long m = mask;
++ int shift = 0, bits = 0;
++
++ if (!mask)
++ return 0;
++ while (!(m & 1)) {
++ m >>= 1;
++ shift++;
++ }
++ while (m & 1) {
++ bits++;
++ m >>= 1;
++ }
++ return (((unsigned long)v * ((1UL << bits) - 1) + 32767) / 65535) << shift;
++}
++
+int
+main(int argc, char *argv[])
+{
-+ uint32_t i, hdr[4], w, h, size;
++ uint32_t hdr[4], w, h, size, x, y;
+ uint64_t npixels;
+ uint64_t *data;
-+ uint32_t *data32;
++ unsigned char *p;
++ unsigned long pixel;
+ FILE *f;
+ int fromstdin;
+ Pixmap pmap, prev;
+ Display *dpy;
++ Visual *vis;
+ Window root;
-+ int scr;
++ int scr, depth;
+ Atom atom_stbg;
+ GC gc;
+ XGCValues gcvalues;
+ XImage *xi;
++ XErrorHandler oldh;
+
+ argv0 = argv[0];
+
_at_@ -173,6 +220,11 @@ index 0000000..ed9c2fa
+
+ scr = DefaultScreen(dpy);
+ root = RootWindow(dpy, scr);
++ vis = DefaultVisual(dpy, scr);
++ depth = DefaultDepth(dpy, scr);
++
++ if (vis->class != TrueColor)
++ die("unsupported visual class
");
+
+ fromstdin = !strcmp(argv[1], "-");
+ f = fromstdin ? stdin : fopen(argv[1], "rb");
_at_@ -189,10 +241,19 @@ index 0000000..ed9c2fa
+ h = ntohl(hdr[3]);
+ npixels = (uint64_t)w * h;
+ if (w == 0 || h == 0 || w > INT_MAX / 4 ||
-+ npixels > UINT32_MAX || npixels > SIZE_MAX / sizeof(uint64_t))
++ npixels > MAX_PIXELS || npixels > UINT32_MAX ||
++ npixels > SIZE_MAX / sizeof(uint64_t))
+ die("invalid farbfeld dimensions
");
+ size = w * h;
+
++ if (!fromstdin) {
++ struct stat st;
++
++ if (fstat(fileno(f), &st) == 0 && S_ISREG(st.st_mode) &&
++ (uint64_t)st.st_size < sizeof(hdr) + npixels * sizeof(uint64_t))
++ die("fread: unexpected end of file
");
++ }
++
+ data = malloc(size * sizeof(uint64_t));
+ if (!data)
+ die("malloc: %s
", strerror(errno));
_at_@ -204,29 +265,29 @@ index 0000000..ed9c2fa
+ if (!fromstdin)
+ fclose(f);
+
-+ data32 = malloc(size * sizeof(uint32_t));
-+ if (!data32) {
++ xi = XCreateImage(dpy, vis, depth, ZPixmap, 0, NULL, w, h, 32, 0);
++ if (!xi) {
++ free(data);
++ die("XCreateImage failed
");
++ }
++ xi->data = malloc((size_t)xi->bytes_per_line * h);
++ if (!xi->data) {
++ XDestroyImage(xi);
+ free(data);
+ die("malloc: %s
", strerror(errno));
+ }
-+
-+ for (i = 0; i < size; i++) {
-+ uint64_t p = data[i];
-+ data32[i] = (p & 0x00000000000000FF) << 16 |
-+ (p & 0x0000000000FF0000) >> 8 |
-+ (p & 0x000000FF00000000) >> 32;
++ p = (unsigned char *)data;
++ for (y = 0; y < h; y++) {
++ for (x = 0; x < w; x++, p += 8) {
++ pixel = channel(((unsigned int)p[0] << 8) | p[1], vis->red_mask) |
++ channel(((unsigned int)p[2] << 8) | p[3], vis->green_mask) |
++ channel(((unsigned int)p[4] << 8) | p[5], vis->blue_mask);
++ XPutPixel(xi, x, y, pixel);
++ }
+ }
+ free(data);
+
-+ xi = XCreateImage(dpy, DefaultVisual(dpy, scr),
-+ DefaultDepth(dpy, scr), ZPixmap, 0,
-+ (char *)data32, w, h, 32, w * 4);
-+ if (!xi) {
-+ free(data32);
-+ die("XCreateImage failed
");
-+ }
-+
-+ pmap = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, scr));
++ pmap = XCreatePixmap(dpy, root, w, h, depth);
+ if (!pmap) {
+ XDestroyImage(xi);
+ die("XCreatePixmap failed
");
_at_@ -235,8 +296,21 @@ index 0000000..ed9c2fa
+ memset(&gcvalues, 0, sizeof(gcvalues));
+ gcvalues.graphics_exposures = False;
+ gc = XCreateGC(dpy, pmap, GCGraphicsExposures, &gcvalues);
++ if (!gc) {
++ XDestroyImage(xi);
++ die("XCreateGC failed
");
++ }
+
++ oldh = XSetErrorHandler(xerror);
++ xerr = 0;
+ XPutImage(dpy, pmap, gc, xi, 0, 0, 0, 0, w, h);
++ XSync(dpy, False);
++ XSetErrorHandler(oldh);
++ if (xerr) {
++ XDestroyImage(xi);
++ XFreeGC(dpy, gc);
++ die("XPutImage failed
");
++ }
+ XDestroyImage(xi);
+ XFreeGC(dpy, gc);
+
_at_@ -244,17 +318,28 @@ index 0000000..ed9c2fa
+
+ XSetCloseDownMode(dpy, RetainPermanent);
+ atom_stbg = XInternAtom(dpy, "_ST_BACKGROUND_IMAGE", False);
++ oldh = XSetErrorHandler(xerror);
++ xerr = 0;
+ XChangeProperty(dpy, root, atom_stbg, XA_PIXMAP, 32,
+ PropModeReplace, (unsigned char *)&pmap, 1);
-+ killprev(dpy, prev);
-+
++ XSync(dpy, False);
++ XSetErrorHandler(oldh);
++ if (xerr) {
++ XSetCloseDownMode(dpy, DestroyAll);
++ XFreePixmap(dpy, pmap);
++ die("XChangeProperty failed
");
++ }
++ if (prev != None) {
++ usleep(200000);
++ killprev(dpy, prev);
++ }
+ XSync(dpy, False);
+ XCloseDisplay(dpy);
+
+ return 0;
+}
diff --git a/x.c b/x.c
-index d73152b..dad569f 100644
+index d73152b..f75f2aa 100644
--- a/x.c
+++ b/x.c
_at_@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
_at_@ -287,21 +372,58 @@ index d73152b..dad569f 100644
static void ximdestroy(XIM, XPointer, XPointer);
static int xicdestroy(XIC, XPointer, XPointer);
static void xinit(int, int);
-+static void updatexy(void);
+static void bginit(void);
+static Pixmap bginitfromroot(void);
++static void updatexy(void);
static void cresize(int, int);
static void xresize(int, int);
static void xhints(void);
-_at_@ -220,6 +228,7 @@ static DC dc;
+_at_@ -220,6 +228,44 @@ static DC dc;
static XWindow xw;
static XSelection xsel;
static TermWindow win;
+static int posdirty = 1; /* window may have moved; re-sync tile origin */
++static int bggone;
++static int xprobe;
++static int xprobeerr;
++static XErrorHandler oldxerror;
++
++static int
++ispixmap(Pixmap pm, Window *root, unsigned int *depth)
++{
++ XWindowAttributes wa;
++ int x, y, ok;
++ unsigned int w, h, bw;
++
++ xprobe = 1;
++ if (XGetWindowAttributes(xw.dpy, pm, &wa)) {
++ xprobe = 0;
++ return 0;
++ }
++ xprobeerr = 0;
++ ok = XGetGeometry(xw.dpy, pm, root, &x, &y, &w, &h, &bw, depth);
++ xprobe = 0;
++ return ok && !xprobeerr;
++}
++
++static int
++xerror(Display *dpy, XErrorEvent *e)
++{
++ if (xprobe) {
++ xprobeerr = 1;
++ return 0;
++ }
++ if (e->error_code == BadPixmap || e->error_code == BadDrawable ||
++ e->error_code == BadMatch) {
++ bggone = 1;
++ return 0;
++ }
++ return oldxerror ? oldxerror(dpy, e) : 0;
++}
/* Font Ring Cache */
enum {
-_at_@ -510,11 +519,29 @@ propnotify(XEvent *e)
+_at_@ -510,11 +556,29 @@ propnotify(XEvent *e)
Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
xpev = &e->xproperty;
_at_@ -332,7 +454,7 @@ index d73152b..dad569f 100644
}
void
-_at_@ -536,6 +563,9 @@ selnotify(XEvent *e)
+_at_@ -536,6 +600,9 @@ selnotify(XEvent *e)
if (property == None)
return;
_at_@ -342,7 +464,7 @@ index d73152b..dad569f 100644
do {
if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
BUFSIZ/4, False, AnyPropertyType,
-_at_@ -552,9 +582,12 @@ selnotify(XEvent *e)
+_at_@ -552,9 +619,12 @@ selnotify(XEvent *e)
* data has been transferred. We won't need to receive
* PropertyNotify events anymore.
*/
_at_@ -358,7 +480,7 @@ index d73152b..dad569f 100644
}
if (type == incratom) {
-_at_@ -563,9 +596,12 @@ selnotify(XEvent *e)
+_at_@ -563,9 +633,12 @@ selnotify(XEvent *e)
* when the selection owner does send us the next
* chunk of data.
*/
_at_@ -373,29 +495,62 @@ index d73152b..dad569f 100644
/*
* Deleting the property is the transfer start signal.
-_at_@ -851,9 +887,9 @@ xsetcolorname(int x, const char *name)
+_at_@ -851,9 +924,13 @@ xsetcolorname(int x, const char *name)
void
xclear(int x1, int y1, int x2, int y2)
{
- XftDrawRect(xw.draw,
- &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
- x1, y1, x2-x1, y2-y1);
++ if (IS_SET(MODE_REVERSE)) {
++ XftDrawRect(xw.draw, &dc.col[defaultfg], x1, y1, x2-x1, y2-y1);
++ return;
++ }
+ if (pseudotransparency)
+ XSetTSOrigin(xw.dpy, xw.bggc, -win.x, -win.y);
+ XFillRectangle(xw.dpy, xw.buf, xw.bggc, x1, y1, x2-x1, y2-y1);
}
void
-_at_@ -1242,6 +1278,77 @@ xinit(int cols, int rows)
+_at_@ -1137,6 +1214,7 @@ xinit(int cols, int rows)
+
+ if (!(xw.dpy = XOpenDisplay(NULL)))
+ die("can't open display
");
++ oldxerror = XSetErrorHandler(xerror);
+ xw.scr = XDefaultScreen(xw.dpy);
+ xw.vis = XDefaultVisual(xw.dpy, xw.scr);
+
+_at_@ -1242,6 +1320,87 @@ xinit(int cols, int rows)
xsel.xtarget = XA_STRING;
}
+void
-+updatexy(void)
++bginit(void)
+{
-+ Window child;
-+ XTranslateCoordinates(xw.dpy, xw.win, DefaultRootWindow(xw.dpy), 0, 0,
-+ &win.x, &win.y, &child);
++ XGCValues gcvalues;
++ Pixmap bgpmap;
++
++ if (xw.bggc)
++ XFreeGC(xw.dpy, xw.bggc);
++
++ memset(&gcvalues, 0, sizeof(gcvalues));
++ xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
++
++ bgpmap = bginitfromroot();
++
++ if (bgpmap == None) {
++ XSetForeground(xw.dpy, xw.bggc, dc.col[defaultbg].pixel);
++ XSetFillStyle(xw.dpy, xw.bggc, FillSolid);
++ }
++
++ if (pseudotransparency) {
++ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
++ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
++ }
++
++ xw.stbg_atom = XInternAtom(xw.dpy, "_ST_BACKGROUND_IMAGE", False);
++ xw.netwmstate_atom = XInternAtom(xw.dpy, "_NET_WM_STATE", False);
++ XSelectInput(xw.dpy, DefaultRootWindow(xw.dpy), PropertyChangeMask);
+}
+
+/*
_at_@ -427,44 +582,33 @@ index d73152b..dad569f 100644
+ pmap = *(Pixmap *)data;
+ XFree(data);
+
++ {
++ Window rr;
++ unsigned int dep;
++
++ if (!ispixmap(pmap, &rr, &dep) ||
++ rr != DefaultRootWindow(xw.dpy) ||
++ dep != DefaultDepth(xw.dpy, xw.scr))
++ return None;
++ }
++
+ XSetTile(xw.dpy, xw.bggc, pmap);
+ XSetFillStyle(xw.dpy, xw.bggc, FillTiled);
+ return pmap;
+}
+
+void
-+bginit(void)
++updatexy(void)
+{
-+ XGCValues gcvalues;
-+ Pixmap bgpmap;
-+
-+ if (xw.bggc)
-+ XFreeGC(xw.dpy, xw.bggc);
-+
-+ memset(&gcvalues, 0, sizeof(gcvalues));
-+ xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
-+
-+ bgpmap = bginitfromroot();
-+
-+ if (bgpmap == None) {
-+ XSetForeground(xw.dpy, xw.bggc, dc.col[defaultbg].pixel);
-+ XSetFillStyle(xw.dpy, xw.bggc, FillSolid);
-+ }
-+
-+ if (pseudotransparency) {
-+ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
-+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
-+ }
-+
-+ xw.stbg_atom = XInternAtom(xw.dpy, "_ST_BACKGROUND_IMAGE", False);
-+ xw.netwmstate_atom = XInternAtom(xw.dpy, "_NET_WM_STATE", False);
-+ XSelectInput(xw.dpy, DefaultRootWindow(xw.dpy), PropertyChangeMask);
++ Window child;
++ XTranslateCoordinates(xw.dpy, xw.win, DefaultRootWindow(xw.dpy), 0, 0,
++ &win.x, &win.y, &child);
+}
+
int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
-_at_@ -1482,7 +1589,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+_at_@ -1482,7 +1641,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
xclear(winx, winy + win.ch, winx + width, win.h);
/* Clean up the region we want to draw to. */
_at_@ -476,10 +620,15 @@ index d73152b..dad569f 100644
/* Set the clip region because Xft is sometimes dirty. */
r.x = 0;
-_at_@ -1651,6 +1761,12 @@ xsettitle(char *p)
+_at_@ -1651,6 +1813,17 @@ xsettitle(char *p)
int
xstartdraw(void)
{
++ if (bggone && IS_SET(MODE_VISIBLE)) {
++ bggone = 0;
++ bginit();
++ tfulldirt();
++ }
+ if (pseudotransparency && IS_SET(MODE_VISIBLE)) {
+ if (posdirty) {
+ updatexy();
_at_@ -489,7 +638,7 @@ index d73152b..dad569f 100644
return IS_SET(MODE_VISIBLE);
}
-_at_@ -1914,9 +2030,15 @@ cmessage(XEvent *e)
+_at_@ -1914,9 +2087,15 @@ cmessage(XEvent *e)
void
resize(XEvent *e)
{
_at_@ -507,7 +656,7 @@ index d73152b..dad569f 100644
cresize(e->xconfigure.width, e->xconfigure.height);
}
-_at_@ -2100,6 +2222,7 @@ run:
+_at_@ -2100,6 +2279,7 @@ run:
rows = MAX(rows, 1);
tnew(cols, rows);
xinit(cols, rows);
Received on Sun Nov 08 2026 - 17:15:50 CET
This archive was generated by hypermail 2.3.0
: Sun Nov 08 2026 - 17:24:54 CET