commit ee8825185dc03e787434e7a00efd3fd6a78dad42
Author: Matthias Schoth <[email protected]>
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
@@ -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 <[email protected]>
-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
");
 +
 +      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
");
 +      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));
@@ -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
");
@@ -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);
 +
@@ -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
 @@ -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
");
++      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);


Reply via email to