sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

commit ee8825185dc03e787434e7a00efd3fd6a78dad42
parent b2bcc0bde65b019274bfbd64d1a1a290ba6dfb62
Author: Matthias Schoth <mschoth@gmail.com>
Date:   Sun,  8 Nov 2026 17:13:22 +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

Diffstat:
Mst.suckless.org/patches/background_image/st-background-image-0.9.3.diff | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 223 insertions(+), 74 deletions(-)

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 @@ -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@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 @@ -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 @@ -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 -@@ -0,0 +1,179 @@ +@@ -0,0 +1,264 @@ +/* See LICENSE file for copyright and license details. */ +/* Build: cc -o stbg stbg.c -lX11 */ +#include <errno.h> @@ -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, ...) @@ -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); @@ -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]; + @@ -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\n"); + + fromstdin = !strcmp(argv[1], "-"); + f = fromstdin ? stdin : fopen(argv[1], "rb"); @@ -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\n"); + 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\n"); ++ } ++ + data = malloc(size * sizeof(uint64_t)); + if (!data) + die("malloc: %s\n", strerror(errno)); @@ -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\n"); ++ } ++ xi->data = malloc((size_t)xi->bytes_per_line * h); ++ if (!xi->data) { ++ XDestroyImage(xi); + free(data); + die("malloc: %s\n", 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\n"); -+ } -+ -+ pmap = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, scr)); ++ pmap = XCreatePixmap(dpy, root, w, h, depth); + if (!pmap) { + XDestroyImage(xi); + die("XCreatePixmap failed\n"); @@ -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\n"); ++ } + ++ 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\n"); ++ } + XDestroyImage(xi); + XFreeGC(dpy, gc); + @@ -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\n"); ++ } ++ 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 @@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec; @@ -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); -@@ -220,6 +228,7 @@ static DC dc; +@@ -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 { -@@ -510,11 +519,29 @@ propnotify(XEvent *e) +@@ -510,11 +556,29 @@ propnotify(XEvent *e) Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); xpev = &e->xproperty; @@ -332,7 +454,7 @@ index d73152b..dad569f 100644 } void -@@ -536,6 +563,9 @@ selnotify(XEvent *e) +@@ -536,6 +600,9 @@ selnotify(XEvent *e) if (property == None) return; @@ -342,7 +464,7 @@ index d73152b..dad569f 100644 do { if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, BUFSIZ/4, False, AnyPropertyType, -@@ -552,9 +582,12 @@ selnotify(XEvent *e) +@@ -552,9 +619,12 @@ selnotify(XEvent *e) * data has been transferred. We won't need to receive * PropertyNotify events anymore. */ @@ -358,7 +480,7 @@ index d73152b..dad569f 100644 } if (type == incratom) { -@@ -563,9 +596,12 @@ selnotify(XEvent *e) +@@ -563,9 +633,12 @@ selnotify(XEvent *e) * when the selection owner does send us the next * chunk of data. */ @@ -373,29 +495,62 @@ index d73152b..dad569f 100644 /* * Deleting the property is the transfer start signal. -@@ -851,9 +887,9 @@ xsetcolorname(int x, const char *name) +@@ -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 -@@ -1242,6 +1278,77 @@ xinit(int cols, int rows) +@@ -1137,6 +1214,7 @@ xinit(int cols, int rows) + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); ++ oldxerror = XSetErrorHandler(xerror); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + +@@ -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); +} + +/* @@ -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) { -@@ -1482,7 +1589,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i +@@ -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. */ @@ -476,10 +620,15 @@ index d73152b..dad569f 100644 /* Set the clip region because Xft is sometimes dirty. */ r.x = 0; -@@ -1651,6 +1761,12 @@ xsettitle(char *p) +@@ -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(); @@ -489,7 +638,7 @@ index d73152b..dad569f 100644 return IS_SET(MODE_VISIBLE); } -@@ -1914,9 +2030,15 @@ cmessage(XEvent *e) +@@ -1914,9 +2087,15 @@ cmessage(XEvent *e) void resize(XEvent *e) { @@ -507,7 +656,7 @@ index d73152b..dad569f 100644 cresize(e->xconfigure.width, e->xconfigure.height); } -@@ -2100,6 +2222,7 @@ run: +@@ -2100,6 +2279,7 @@ run: rows = MAX(rows, 1); tnew(cols, rows); xinit(cols, rows);