[wiki] [sites] [st][patches][background_image] Fix 0.9.3 X client and 0.8.x leaks || Matthias Schoth

From: git=suckless.org via suckless wiki list <wiki_at_suckless.org>
Date: Sat, 31 Oct 2026 22:54:07 +0100

commit bc7b57379bce0e7f7b3a974724e0f1a1c754a190
Author: Matthias Schoth <mschoth_at_gmail.com>
Date: Sat Oct 31 22:45:35 2026 +0100

    [st][patches][background_image] Fix 0.9.3 X client and 0.8.x leaks
    
    - 0.9.3: publish the new pixmap before killing the previous owner; only
      one retained client/pixmap remains, so repeated updates no longer clog
      the X connection or exhaust client slots.
    - 0.8.4/0.8.5: free the previous GC and pixmap on reload; failed loads
      keep the current background, and loader error paths no longer leak.
    - USR1 reload: use sigaction() and a self-pipe, reloading from the main
      loop instead of calling Xlib in the signal handler. Applies to 0.8.4
      and 0.8.5.

diff --git a/st.suckless.org/patches/background_image/st-background-image-0.8.4.diff b/st.suckless.org/patches/background_image/st-background-image-0.8.4.diff
index f32c9476..d7ac5acf 100644
--- a/st.suckless.org/patches/background_image/st-background-image-0.8.4.diff
+++ b/st.suckless.org/patches/background_image/st-background-image-0.8.4.diff
_at_@ -1,12 +1,12 @@
-From 2c984d74ca15806dcfa174b7e75f48c0d01a49bf Mon Sep 17 00:00:00 2001
+From ac472ae8b588b8ef6be7cbdd4487210f07349a3c Mon Sep 17 00:00:00 2001
 From: Matthias Schoth <mschoth_at_gmail.com>
 Date: Thu, 17 Feb 2022 00:23:23 +0100
 Subject: [PATCH] Implements background image and pseudo transparancy support.
 
 ---
  config.def.h | 8 +++
- x.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++----
- 2 files changed, 139 insertions(+), 10 deletions(-)
+ x.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++---
+ 2 files changed, 166 insertions(+), 10 deletions(-)
 
 diff --git a/config.def.h b/config.def.h
 index 6f05dce..3d352db 100644
_at_@ -28,7 +28,7 @@ index 6f05dce..3d352db 100644
   * What program is execed by st depends of these precedence rules:
   * 1: program passed with -e
 diff --git a/x.c b/x.c
-index 210f184..5ecb8e5 100644
+index 210f184..a5fc827 100644
 --- a/x.c
 +++ b/x.c
 _at_@ -14,6 +14,7 @@
_at_@ -47,15 +47,16 @@ index 210f184..5ecb8e5 100644
          int ch; /* char height */
          int cw; /* char width */
          int mode; /* window state/mode flags */
-_at_@ -101,6 +103,7 @@ typedef struct {
+_at_@ -101,6 +103,8 @@ typedef struct {
                  XVaNestedList spotlist;
          } ime;
          Draw draw;
 + GC bggc; /* Graphics Context for background */
++ Pixmap bgpmap; /* background image pixmap */
          Visual *vis;
          XSetWindowAttributes attrs;
          int scr;
-_at_@ -151,6 +154,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
+_at_@ -151,6 +155,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
  static void ximdestroy(XIM, XPointer, XPointer);
  static int xicdestroy(XIC, XPointer, XPointer);
  static void xinit(int, int);
_at_@ -65,7 +66,7 @@ index 210f184..5ecb8e5 100644
  static void cresize(int, int);
  static void xresize(int, int);
  static void xhints(void);
-_at_@ -502,6 +508,12 @@ propnotify(XEvent *e)
+_at_@ -502,6 +509,12 @@ propnotify(XEvent *e)
                           xpev->atom == clipboard)) {
                  selnotify(e);
          }
_at_@ -78,7 +79,7 @@ index 210f184..5ecb8e5 100644
  }
  
  void
-_at_@ -532,7 +544,8 @@ selnotify(XEvent *e)
+_at_@ -532,7 +545,8 @@ selnotify(XEvent *e)
                          return;
                  }
  
_at_@ -88,7 +89,7 @@ index 210f184..5ecb8e5 100644
                          /*
                           * If there is some PropertyNotify with no data, then
                           * this is the signal of the selection owner that all
-_at_@ -550,9 +563,11 @@ selnotify(XEvent *e)
+_at_@ -550,9 +564,11 @@ selnotify(XEvent *e)
                           * when the selection owner does send us the next
                           * chunk of data.
                           */
_at_@ -102,7 +103,7 @@ index 210f184..5ecb8e5 100644
  
                          /*
                           * Deleting the property is the transfer start signal.
-_at_@ -820,9 +835,9 @@ xsetcolorname(int x, const char *name)
+_at_@ -820,9 +836,9 @@ xsetcolorname(int x, const char *name)
  void
  xclear(int x1, int y1, int x2, int y2)
  {
_at_@ -115,7 +116,7 @@ index 210f184..5ecb8e5 100644
  }
  
  void
-_at_@ -1207,6 +1222,100 @@ xinit(int cols, int rows)
+_at_@ -1207,6 +1223,126 @@ xinit(int cols, int rows)
                  xsel.xtarget = XA_STRING;
  }
  
_at_@ -134,44 +135,53 @@ index 210f184..5ecb8e5 100644
 +loadff(const char *filename)
 +{
 + uint32_t i, hdr[4], w, h, size;
-+ uint64_t *data;
-+ FILE *f = fopen(filename, "rb");
++ uint64_t npixels, *data;
++ FILE *f;
++ XImage *xi;
 +
-+ if (f == NULL) {
++ if (!(f = fopen(filename, "rb"))) {
 + fprintf(stderr, "could not load background image.
");
 + return NULL;
 + }
 +
-+ if (fread(hdr, sizeof(*hdr), LEN(hdr), f) != LEN(hdr))
-+ if (ferror(f)) {
-+ fprintf(stderr, "fread:");
-+ return NULL;
-+ }
-+ else {
-+ fprintf(stderr, "fread: Unexpected end of file
");
-+ return NULL;
-+ }
++ if (fread(hdr, sizeof(*hdr), LEN(hdr), f) != LEN(hdr)) {
++ fprintf(stderr, "fread: %s
", ferror(f) ? "read error" :
++ "unexpected end of file");
++ fclose(f);
++ return NULL;
++ }
 +
 + if (memcmp("farbfeld", hdr, sizeof("farbfeld") - 1)) {
-+ fprintf(stderr, "Invalid magic value");
++ fprintf(stderr, "Invalid magic value
");
++ fclose(f);
 + return NULL;
 + }
 +
 + w = ntohl(hdr[2]);
 + h = ntohl(hdr[3]);
++ npixels = (uint64_t)w * h;
++ if (w == 0 || h == 0 || w > INT_MAX / 8 ||
++ npixels > UINT32_MAX || npixels > SIZE_MAX / sizeof(*data)) {
++ fprintf(stderr, "Invalid image dimensions
");
++ fclose(f);
++ return NULL;
++ }
 + size = w * h;
-+ data = malloc(size * sizeof(uint64_t));
 +
-+ if (fread(data, sizeof(uint64_t), size, f) != size)
-+ if (ferror(f)) {
-+ fprintf(stderr, "fread:");
-+ return NULL;
-+ }
-+ else {
-+ fprintf(stderr, "fread: Unexpected end of file");
-+ return NULL;
-+ }
++ data = malloc(size * sizeof(*data));
++ if (!data) {
++ fprintf(stderr, "malloc: %s
", strerror(errno));
++ fclose(f);
++ return NULL;
++ }
 +
++ if (fread(data, sizeof(*data), size, f) != size) {
++ fprintf(stderr, "fread: %s
", ferror(f) ? "read error" :
++ "unexpected end of file");
++ free(data);
++ fclose(f);
++ return NULL;
++ }
 + fclose(f);
 +
 + for (i = 0; i < size; i++)
_at_@ -179,9 +189,13 @@ index 210f184..5ecb8e5 100644
 + (data[i] & 0x0000000000FF0000) >> 8 |
 + (data[i] & 0x000000FF00000000) >> 32;
 +
-+ XImage *xi = XCreateImage(xw.dpy, DefaultVisual(xw.dpy, xw.scr),
-+ DefaultDepth(xw.dpy, xw.scr), ZPixmap, 0,
-+ (char *)data, w, h, 32, w * 8);
++ xi = XCreateImage(xw.dpy, DefaultVisual(xw.dpy, xw.scr),
++ DefaultDepth(xw.dpy, xw.scr), ZPixmap, 0,
++ (char *)data, w, h, 32, w * 8);
++ if (!xi) {
++ free(data);
++ return NULL;
++ }
 + xi->bits_per_pixel = 64;
 + return xi;
 +}
_at_@ -193,18 +207,31 @@ index 210f184..5ecb8e5 100644
 +bginit()
 +{
 + XGCValues gcvalues;
-+ Drawable bgimg;
 + XImage *bgxi = loadff(bgfile);
 +
++ if (!bgxi) {
++ if (xw.bggc)
++ return;
++ memset(&gcvalues, 0, sizeof(gcvalues));
++ xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
++ XSetForeground(xw.dpy, xw.bggc, dc.col[defaultbg].pixel);
++ XSetFillStyle(xw.dpy, xw.bggc, FillSolid);
++ return;
++ }
++
++ if (xw.bggc)
++ XFreeGC(xw.dpy, xw.bggc);
++ if (xw.bgpmap)
++ XFreePixmap(xw.dpy, xw.bgpmap);
++
 + memset(&gcvalues, 0, sizeof(gcvalues));
 + xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
-+ if (!bgxi) return;
-+ bgimg = XCreatePixmap(xw.dpy, xw.win, bgxi->width, bgxi->height,
-+ DefaultDepth(xw.dpy, xw.scr));
-+ XPutImage(xw.dpy, bgimg, dc.gc, bgxi, 0, 0, 0, 0, bgxi->width,
++ xw.bgpmap = XCreatePixmap(xw.dpy, xw.win, bgxi->width, bgxi->height,
++ DefaultDepth(xw.dpy, xw.scr));
++ XPutImage(xw.dpy, xw.bgpmap, dc.gc, bgxi, 0, 0, 0, 0, bgxi->width,
 + bgxi->height);
 + XDestroyImage(bgxi);
-+ XSetTile(xw.dpy, xw.bggc, bgimg);
++ XSetTile(xw.dpy, xw.bggc, xw.bgpmap);
 + XSetFillStyle(xw.dpy, xw.bggc, FillTiled);
 + if (pseudotransparency) {
 + updatexy();
_at_@ -216,7 +243,7 @@ index 210f184..5ecb8e5 100644
  int
  xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
  {
-_at_@ -1447,7 +1556,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+_at_@ -1447,7 +1583,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_@ -228,7 +255,7 @@ index 210f184..5ecb8e5 100644
  
          /* Set the clip region because Xft is sometimes dirty. */
          r.x = 0;
-_at_@ -1855,9 +1967,17 @@ cmessage(XEvent *e)
+_at_@ -1855,9 +1994,17 @@ cmessage(XEvent *e)
  void
  resize(XEvent *e)
  {
_at_@ -249,7 +276,7 @@ index 210f184..5ecb8e5 100644
          cresize(e->xconfigure.width, e->xconfigure.height);
  }
  
-_at_@ -2041,6 +2161,7 @@ run:
+_at_@ -2041,6 +2188,7 @@ run:
          rows = MAX(rows, 1);
          tnew(cols, rows);
          xinit(cols, rows);
_at_@ -258,5 +285,5 @@ index 210f184..5ecb8e5 100644
          selinit();
          run();
 --
-2.35.1
+2.55.0
 
diff --git a/st.suckless.org/patches/background_image/st-background-image-0.8.5.diff b/st.suckless.org/patches/background_image/st-background-image-0.8.5.diff
index 9f6c5495..67b630a0 100644
--- a/st.suckless.org/patches/background_image/st-background-image-0.8.5.diff
+++ b/st.suckless.org/patches/background_image/st-background-image-0.8.5.diff
_at_@ -1,12 +1,12 @@
-From 4ce17bbc340ca1f93f9be686f4a796797f9ca6d5 Mon Sep 17 00:00:00 2001
+From d9f5d917ac2016f3f375f4ada575e6b6fc5e2d1b Mon Sep 17 00:00:00 2001
 From: Matthias Schoth <mschoth_at_gmail.com>
 Date: Sat, 16 Apr 2022 14:21:22 +0200
 Subject: [PATCH] Implements background image and pseudo transparancy support.
 
 ---
  config.def.h | 8 +++
- x.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++----
- 2 files changed, 139 insertions(+), 10 deletions(-)
+ x.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++---
+ 2 files changed, 166 insertions(+), 10 deletions(-)
 
 diff --git a/config.def.h b/config.def.h
 index 91ab8ca..ad19e3d 100644
_at_@ -28,7 +28,7 @@ index 91ab8ca..ad19e3d 100644
   * What program is execed by st depends of these precedence rules:
   * 1: program passed with -e
 diff --git a/x.c b/x.c
-index 8a16faa..fedef53 100644
+index 8a16faa..cab0058 100644
 --- a/x.c
 +++ b/x.c
 _at_@ -14,6 +14,7 @@
_at_@ -47,15 +47,16 @@ index 8a16faa..fedef53 100644
          int ch; /* char height */
          int cw; /* char width */
          int mode; /* window state/mode flags */
-_at_@ -101,6 +103,7 @@ typedef struct {
+_at_@ -101,6 +103,8 @@ typedef struct {
                  XVaNestedList spotlist;
          } ime;
          Draw draw;
 + GC bggc; /* Graphics Context for background */
++ Pixmap bgpmap; /* background image pixmap */
          Visual *vis;
          XSetWindowAttributes attrs;
          int scr;
-_at_@ -151,6 +154,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
+_at_@ -151,6 +155,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
  static void ximdestroy(XIM, XPointer, XPointer);
  static int xicdestroy(XIC, XPointer, XPointer);
  static void xinit(int, int);
_at_@ -65,7 +66,7 @@ index 8a16faa..fedef53 100644
  static void cresize(int, int);
  static void xresize(int, int);
  static void xhints(void);
-_at_@ -504,6 +510,12 @@ propnotify(XEvent *e)
+_at_@ -504,6 +511,12 @@ propnotify(XEvent *e)
                           xpev->atom == clipboard)) {
                  selnotify(e);
          }
_at_@ -78,7 +79,7 @@ index 8a16faa..fedef53 100644
  }
  
  void
-_at_@ -534,7 +546,8 @@ selnotify(XEvent *e)
+_at_@ -534,7 +547,8 @@ selnotify(XEvent *e)
                          return;
                  }
  
_at_@ -88,7 +89,7 @@ index 8a16faa..fedef53 100644
                          /*
                           * If there is some PropertyNotify with no data, then
                           * this is the signal of the selection owner that all
-_at_@ -552,9 +565,11 @@ selnotify(XEvent *e)
+_at_@ -552,9 +566,11 @@ selnotify(XEvent *e)
                           * when the selection owner does send us the next
                           * chunk of data.
                           */
_at_@ -102,7 +103,7 @@ index 8a16faa..fedef53 100644
  
                          /*
                           * Deleting the property is the transfer start signal.
-_at_@ -835,9 +850,9 @@ xsetcolorname(int x, const char *name)
+_at_@ -835,9 +851,9 @@ xsetcolorname(int x, const char *name)
  void
  xclear(int x1, int y1, int x2, int y2)
  {
_at_@ -115,7 +116,7 @@ index 8a16faa..fedef53 100644
  }
  
  void
-_at_@ -1223,6 +1238,100 @@ xinit(int cols, int rows)
+_at_@ -1223,6 +1239,126 @@ xinit(int cols, int rows)
                  xsel.xtarget = XA_STRING;
  }
  
_at_@ -134,44 +135,53 @@ index 8a16faa..fedef53 100644
 +loadff(const char *filename)
 +{
 + uint32_t i, hdr[4], w, h, size;
-+ uint64_t *data;
-+ FILE *f = fopen(filename, "rb");
++ uint64_t npixels, *data;
++ FILE *f;
++ XImage *xi;
 +
-+ if (f == NULL) {
++ if (!(f = fopen(filename, "rb"))) {
 + fprintf(stderr, "Can not open background image file
");
 + return NULL;
 + }
 +
-+ if (fread(hdr, sizeof(*hdr), LEN(hdr), f) != LEN(hdr))
-+ if (ferror(f)) {
-+ fprintf(stderr, "fread:");
-+ return NULL;
-+ }
-+ else {
-+ fprintf(stderr, "fread: Unexpected end of file
");
-+ return NULL;
-+ }
++ if (fread(hdr, sizeof(*hdr), LEN(hdr), f) != LEN(hdr)) {
++ fprintf(stderr, "fread: %s
", ferror(f) ? "read error" :
++ "unexpected end of file");
++ fclose(f);
++ return NULL;
++ }
 +
 + if (memcmp("farbfeld", hdr, sizeof("farbfeld") - 1)) {
 + fprintf(stderr, "Invalid magic value
");
++ fclose(f);
 + return NULL;
 + }
 +
 + w = ntohl(hdr[2]);
 + h = ntohl(hdr[3]);
++ npixels = (uint64_t)w * h;
++ if (w == 0 || h == 0 || w > INT_MAX / 8 ||
++ npixels > UINT32_MAX || npixels > SIZE_MAX / sizeof(*data)) {
++ fprintf(stderr, "Invalid image dimensions
");
++ fclose(f);
++ return NULL;
++ }
 + size = w * h;
-+ data = malloc(size * sizeof(uint64_t));
 +
-+ if (fread(data, sizeof(uint64_t), size, f) != size)
-+ if (ferror(f)) {
-+ fprintf(stderr, "fread:");
-+ return NULL;
-+ }
-+ else {
-+ fprintf(stderr, "fread: Unexpected end of file
");
-+ return NULL;
-+ }
++ data = malloc(size * sizeof(*data));
++ if (!data) {
++ fprintf(stderr, "malloc: %s
", strerror(errno));
++ fclose(f);
++ return NULL;
++ }
 +
++ if (fread(data, sizeof(*data), size, f) != size) {
++ fprintf(stderr, "fread: %s
", ferror(f) ? "read error" :
++ "unexpected end of file");
++ free(data);
++ fclose(f);
++ return NULL;
++ }
 + fclose(f);
 +
 + for (i = 0; i < size; i++)
_at_@ -179,9 +189,13 @@ index 8a16faa..fedef53 100644
 + (data[i] & 0x0000000000FF0000) >> 8 |
 + (data[i] & 0x000000FF00000000) >> 32;
 +
-+ XImage *xi = XCreateImage(xw.dpy, DefaultVisual(xw.dpy, xw.scr),
-+ DefaultDepth(xw.dpy, xw.scr), ZPixmap, 0,
-+ (char *)data, w, h, 32, w * 8);
++ xi = XCreateImage(xw.dpy, DefaultVisual(xw.dpy, xw.scr),
++ DefaultDepth(xw.dpy, xw.scr), ZPixmap, 0,
++ (char *)data, w, h, 32, w * 8);
++ if (!xi) {
++ free(data);
++ return NULL;
++ }
 + xi->bits_per_pixel = 64;
 + return xi;
 +}
_at_@ -193,18 +207,31 @@ index 8a16faa..fedef53 100644
 +bginit()
 +{
 + XGCValues gcvalues;
-+ Drawable bgimg;
 + XImage *bgxi = loadff(bgfile);
 +
++ if (!bgxi) {
++ if (xw.bggc)
++ return;
++ memset(&gcvalues, 0, sizeof(gcvalues));
++ xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
++ XSetForeground(xw.dpy, xw.bggc, dc.col[defaultbg].pixel);
++ XSetFillStyle(xw.dpy, xw.bggc, FillSolid);
++ return;
++ }
++
++ if (xw.bggc)
++ XFreeGC(xw.dpy, xw.bggc);
++ if (xw.bgpmap)
++ XFreePixmap(xw.dpy, xw.bgpmap);
++
 + memset(&gcvalues, 0, sizeof(gcvalues));
 + xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
-+ if (!bgxi) return;
-+ bgimg = XCreatePixmap(xw.dpy, xw.win, bgxi->width, bgxi->height,
-+ DefaultDepth(xw.dpy, xw.scr));
-+ XPutImage(xw.dpy, bgimg, dc.gc, bgxi, 0, 0, 0, 0, bgxi->width,
++ xw.bgpmap = XCreatePixmap(xw.dpy, xw.win, bgxi->width, bgxi->height,
++ DefaultDepth(xw.dpy, xw.scr));
++ XPutImage(xw.dpy, xw.bgpmap, dc.gc, bgxi, 0, 0, 0, 0, bgxi->width,
 + bgxi->height);
 + XDestroyImage(bgxi);
-+ XSetTile(xw.dpy, xw.bggc, bgimg);
++ XSetTile(xw.dpy, xw.bggc, xw.bgpmap);
 + XSetFillStyle(xw.dpy, xw.bggc, FillTiled);
 + if (pseudotransparency) {
 + updatexy();
_at_@ -216,7 +243,7 @@ index 8a16faa..fedef53 100644
  int
  xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
  {
-_at_@ -1463,7 +1572,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+_at_@ -1463,7 +1599,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_@ -228,7 +255,7 @@ index 8a16faa..fedef53 100644
  
          /* Set the clip region because Xft is sometimes dirty. */
          r.x = 0;
-_at_@ -1886,9 +1998,17 @@ cmessage(XEvent *e)
+_at_@ -1886,9 +2025,17 @@ cmessage(XEvent *e)
  void
  resize(XEvent *e)
  {
_at_@ -249,7 +276,7 @@ index 8a16faa..fedef53 100644
          cresize(e->xconfigure.width, e->xconfigure.height);
  }
  
-_at_@ -2072,6 +2192,7 @@ run:
+_at_@ -2072,6 +2219,7 @@ run:
          rows = MAX(rows, 1);
          tnew(cols, rows);
          xinit(cols, rows);
_at_@ -258,5 +285,5 @@ index 8a16faa..fedef53 100644
          selinit();
          run();
 --
-2.35.1
+2.55.0
 
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 9e9d4190..4478fcd4 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 b43b4842d62d800da712ef7d6244967daa4ff096 Mon Sep 17 00:00:00 2001
+From c2ae9bfcb131b13435398922b1baf88c87df8908 Mon Sep 17 00:00:00 2001
 From: Matthias Schoth <mschoth_at_gmail.com>
-Date: Tue, 18 Aug 2026 19:32:18 +0200
+Date: Sat, 31 Oct 2026 22:41:01 +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_@ -17,13 +17,19 @@ stbg reads a farbfeld image from a file or stdin (using -) and sets the
 property. It is not built by default; compile separately with:
 
     cc -o stbg stbg.c -lX11
+
+A new image is decoded and published before the previous owner is
+released, so a failed read leaves the current background untouched.
+Only the current pixmap is kept alive; replacing the background kills
+the client that owns the previous one, so periodic updates do not
+accumulate X clients or server-side image memory.
 ---
- config.def.h | 6 +++
+ config.def.h | 6 ++
  st.c | 2 +-
  st.h | 1 +
- stbg.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++
- x.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++----
- 5 files changed, 248 insertions(+), 10 deletions(-)
+ stbg.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ x.c | 127 +++++++++++++++++++++++++++++++++---
+ 5 files changed, 305 insertions(+), 10 deletions(-)
  create mode 100644 stbg.c
 
 diff --git a/config.def.h b/config.def.h
_at_@ -70,13 +76,14 @@ 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..80bc4cf
+index 0000000..ed9c2fa
 --- /dev/null
 +++ b/stbg.c
-_at_@ -0,0 +1,122 @@
+_at_@ -0,0 +1,179 @@
 +/* See LICENSE file for copyright and license details. */
 +/* Build: cc -o stbg stbg.c -lX11 */
 +#include <errno.h>
++#include <limits.h>
 +#include <stdarg.h>
 +#include <stdio.h>
 +#include <stdlib.h>
_at_@ -88,6 +95,7 @@ index 0000000..80bc4cf
 +#include <X11/Xatom.h>
 +
 +static char *argv0;
++static int xerror(Display *dpy, XErrorEvent *e) { (void)dpy; (void)e; return 0; }
 +
 +static void
 +die(const char *errstr, ...)
_at_@ -107,15 +115,52 @@ index 0000000..80bc4cf
 + die("usage: %s file.ff
       %s - < file.ff
", argv0, argv0);
 +}
 +
++static Pixmap
++getprev(Display *dpy, Window root)
++{
++ Atom prop = XInternAtom(dpy, "_ST_BACKGROUND_IMAGE", True);
++ Atom actual_type;
++ int actual_format;
++ unsigned long nitems, bytes_after;
++ unsigned char *prop_data = NULL;
++ Pixmap prev = None;
++
++ if (prop == None)
++ return None;
++ if (XGetWindowProperty(dpy, root, prop, 0, 1, False, XA_PIXMAP,
++ &actual_type, &actual_format, &nitems,
++ &bytes_after, &prop_data) != Success)
++ return None;
++ if (prop_data && nitems == 1 && actual_type == XA_PIXMAP && actual_format == 32)
++ prev = *(Pixmap *)prop_data;
++ if (prop_data)
++ XFree(prop_data);
++ return prev;
++}
++
++static void
++killprev(Display *dpy, Pixmap prev)
++{
++ XErrorHandler oldh;
++
++ if (prev == None)
++ return;
++ oldh = XSetErrorHandler(xerror);
++ XKillClient(dpy, prev);
++ XSync(dpy, False);
++ XSetErrorHandler(oldh);
++}
++
 +int
 +main(int argc, char *argv[])
 +{
 + uint32_t i, hdr[4], w, h, size;
++ uint64_t npixels;
 + uint64_t *data;
 + uint32_t *data32;
 + FILE *f;
 + int fromstdin;
-+ Pixmap pmap;
++ Pixmap pmap, prev;
 + Display *dpy;
 + Window root;
 + int scr;
_at_@ -148,7 +193,9 @@ index 0000000..80bc4cf
 +
 + w = ntohl(hdr[2]);
 + h = ntohl(hdr[3]);
-+ if (w == 0 || h == 0)
++ npixels = (uint64_t)w * h;
++ if (w == 0 || h == 0 || w > INT_MAX / 4 ||
++ npixels > UINT32_MAX || npixels > SIZE_MAX / sizeof(uint64_t))
 + die("invalid farbfeld dimensions
");
 + size = w * h;
 +
_at_@ -156,14 +203,18 @@ index 0000000..80bc4cf
 + if (!data)
 + die("malloc: %s
", strerror(errno));
 +
-+ if (fread(data, sizeof(uint64_t), size, f) != size)
++ if (fread(data, sizeof(uint64_t), size, f) != size) {
++ free(data);
 + die("fread: unexpected end of file
");
++ }
 + if (!fromstdin)
 + fclose(f);
 +
 + data32 = malloc(size * sizeof(uint32_t));
-+ if (!data32)
++ if (!data32) {
++ free(data);
 + die("malloc: %s
", strerror(errno));
++ }
 +
 + for (i = 0; i < size; i++) {
 + uint64_t p = data[i];
_at_@ -176,8 +227,16 @@ index 0000000..80bc4cf
 + 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));
++ if (!pmap) {
++ XDestroyImage(xi);
++ die("XCreatePixmap failed
");
++ }
 +
 + memset(&gcvalues, 0, sizeof(gcvalues));
 + gcvalues.graphics_exposures = False;
_at_@ -187,12 +246,16 @@ index 0000000..80bc4cf
 + XDestroyImage(xi);
 + XFreeGC(dpy, gc);
 +
++ prev = getprev(dpy, root);
++
++ XSetCloseDownMode(dpy, RetainPermanent);
 + atom_stbg = XInternAtom(dpy, "_ST_BACKGROUND_IMAGE", False);
 + XChangeProperty(dpy, root, atom_stbg, XA_PIXMAP, 32,
 + PropModeReplace, (unsigned char *)&pmap, 1);
-+ XSetCloseDownMode(dpy, RetainPermanent);
++ killprev(dpy, prev);
 +
 + XSync(dpy, False);
++ XCloseDisplay(dpy);
 +
 + return 0;
 +}
diff --git a/st.suckless.org/patches/background_image/st-background-image-signal-reload-0.8.5.diff b/st.suckless.org/patches/background_image/st-background-image-signal-reload-0.8.5.diff
index d323efc9..072cae0a 100644
--- a/st.suckless.org/patches/background_image/st-background-image-signal-reload-0.8.5.diff
+++ b/st.suckless.org/patches/background_image/st-background-image-signal-reload-0.8.5.diff
_at_@ -1,51 +1,130 @@
-From 1317d64817460aee9321e29e5e31c072d59139c6 Mon Sep 17 00:00:00 2001
+From b56c9081dc4c62b7de5357cc73bc10a310f011b8 Mon Sep 17 00:00:00 2001
 From: Matthias Schoth <mschoth_at_gmail.com>
 Date: Sun, 17 Apr 2022 04:01:59 +0200
 Subject: [PATCH] Reload background image on USR1 signal.
 
+Queue reload requests from the signal handler and service them from the
+main loop. Xlib is not async-signal-safe, so calling it directly from the
+handler can crash st or leak X resources when signals arrive while st is
+drawing.
 ---
- x.c | 14 ++++++++++++++
- 1 file changed, 14 insertions(+)
+ x.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 55 insertions(+), 1 deletion(-)
 
 diff --git a/x.c b/x.c
-index fedef53..ac79ac7 100644
+index cab0058..7c9076c 100644
 --- a/x.c
 +++ b/x.c
-_at_@ -156,6 +156,7 @@ static int xicdestroy(XIC, XPointer, XPointer);
+_at_@ -1,5 +1,6 @@
+ /* See LICENSE for license details. */
+ #include <errno.h>
++#include <fcntl.h>
+ #include <math.h>
+ #include <limits.h>
+ #include <locale.h>
+_at_@ -157,6 +158,8 @@ static int xicdestroy(XIC, XPointer, XPointer);
  static void xinit(int, int);
  static void updatexy(void);
  static XImage *loadff(const char *);
-+static void bgreload();
++static void bgreload(int);
++static void bgreloadinit(void);
  static void bginit();
  static void cresize(int, int);
  static void xresize(int, int);
-_at_@ -1305,6 +1306,18 @@ loadff(const char *filename)
+_at_@ -1319,6 +1322,46 @@ loadff(const char *filename)
          return xi;
  }
  
++static int bgreloadpipe[2] = {-1, -1};
++
 +/*
-+ * reload background image on USR1 signal
++ * Xlib is not async-signal-safe, so just wake up the main loop and let it
++ * reload the image there.
 + */
-+void
-+bgreload()
++static void
++bgreload(int sig)
 +{
-+ XFreeGC(xw.dpy, xw.bggc);
-+ bginit();
-+ redraw();
-+ signal(SIGUSR1, bgreload);
++ char c = 0;
++
++ (void)sig;
++ if (bgreloadpipe[1] != -1)
++ (void)write(bgreloadpipe[1], &c, 1);
++}
++
++static void
++bgreloadinit(void)
++{
++ struct sigaction sa;
++ int flags;
++
++ if (pipe(bgreloadpipe) < 0)
++ die("pipe: %s
", strerror(errno));
++
++ flags = fcntl(bgreloadpipe[0], F_GETFL);
++ fcntl(bgreloadpipe[0], F_SETFL, flags | O_NONBLOCK);
++ flags = fcntl(bgreloadpipe[1], F_GETFL);
++ fcntl(bgreloadpipe[1], F_SETFL, flags | O_NONBLOCK);
++ fcntl(bgreloadpipe[0], F_SETFD, FD_CLOEXEC);
++ fcntl(bgreloadpipe[1], F_SETFD, FD_CLOEXEC);
++
++ memset(&sa, 0, sizeof(sa));
++ sa.sa_handler = bgreload;
++ sigemptyset(&sa.sa_mask);
++ sa.sa_flags = SA_RESTART;
++ if (sigaction(SIGUSR1, &sa, NULL) < 0)
++ die("sigaction: %s
", strerror(errno));
 +}
 +
  /*
   * initialize background image
   */
-_at_@ -2193,6 +2206,7 @@ run:
+_at_@ -2046,6 +2089,8 @@ run(void)
+ int w = win.w, h = win.h;
+ fd_set rfd;
+ int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
++ int bgrfd = bgreloadpipe[0];
++ char c;
+ struct timespec seltv, *tv, now, lastblink, trigger;
+ double timeout;
+
+_at_@ -2072,6 +2117,7 @@ run(void)
+ FD_ZERO(&rfd);
+ FD_SET(ttyfd, &rfd);
+ FD_SET(xfd, &rfd);
++ FD_SET(bgrfd, &rfd);
+
+ if (XPending(xw.dpy))
+ timeout = 0; /* existing events might not set xfd */
+_at_@ -2080,13 +2126,20 @@ run(void)
+ seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
+ tv = timeout >= 0 ? &seltv : NULL;
+
+- if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
++ if (pselect(MAX(xfd, MAX(ttyfd, bgrfd))+1, &rfd, NULL, NULL, tv, NULL) < 0) {
+ if (errno == EINTR)
+ continue;
+ die("select failed: %s
", strerror(errno));
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
++ if (FD_ISSET(bgrfd, &rfd)) {
++ while (read(bgrfd, &c, 1) > 0)
++ ;
++ bginit();
++ redraw();
++ }
++
+ if (FD_ISSET(ttyfd, &rfd))
+ ttyread();
+
+_at_@ -2219,6 +2272,7 @@ run:
+ rows = MAX(rows, 1);
          tnew(cols, rows);
          xinit(cols, rows);
++ bgreloadinit();
          bginit();
-+ signal(SIGUSR1, bgreload);
          xsetenv();
          selinit();
- run();
 --
-2.35.3
+2.55.0
 
Received on Sat Oct 31 2026 - 22:54:07 CET

This archive was generated by hypermail 2.3.0 : Sat Oct 31 2026 - 23:00:55 CET