URI:
       [st][patches][background-image] Port to 0.9.3 and major overhaul - sites - public wiki contents of suckless.org
  HTML git clone git://git.suckless.org/sites
   DIR Log
   DIR Files
   DIR Refs
       ---
   DIR commit a7d0eec3224fe452ebbbd0d832a95c9b5240fe5b
   DIR parent 74e79f4d45fca3c8a802ca44cb0daebd225262c8
  HTML Author: Matthias Schoth <mschoth@gmail.com>
       Date:   Sat,  1 Aug 2026 02:37:43 +0200
       
       [st][patches][background-image] Port to 0.9.3 and major overhaul
       
       The new version loads the image via a server-side pixmap published in
       the _ST_BACKGROUND_IMAGE root window property by the companion stbg
       tool. The background is shared between all st instances and can be
       updated live without restarting the terminal.
       
       Includes the st-background-image-0.9.3.diff download as well as links
       to the legacy 0.8.4/0.8.5 patches and their signal-reload variant.
       
       Diffstat:
         M st.suckless.org/patches/background… |      93 +++++++++++++++++++------------
         A st.suckless.org/patches/background… |     391 ++++++++++++++++++++++++++++++
       
       2 files changed, 447 insertions(+), 37 deletions(-)
       ---
   DIR diff --git a/st.suckless.org/patches/background_image/index.md b/st.suckless.org/patches/background_image/index.md
       @@ -4,60 +4,79 @@ background image
        Description
        -----------
        
       -Draws a background image in place of the defaultbg color.
       +Draws a background image in place of the `defaultbg` color. Instead of
       +loading an image file itself, st uses the pixmap published by the
       +companion program `stbg` in the `_ST_BACKGROUND_IMAGE` root window
       +property. Every st instance tiles the same server-side pixmap, so the
       +image data is held in memory only once and all instances stay in sync.
        
       -Notes
       +Download
       +--------
       +
       +* [st-background-image-0.9.3.diff](st-background-image-0.9.3.diff)
       +
       +Usage
        -----
        
       -The path to the image file has to be configured in `config.h` using the variable
       -`bgfile` (patch modifies `config.def.h`, changes made there need to be ported to
       -`config.h` if it already exists). The image format is expected to be
       -[farbfeld](//tools.suckless.org/farbfeld). In case the background image 
       -is smaller than the window size the background will be tiled.
       +The background image is set by the separate `stbg` tool, which reads a
       +[farbfeld](//tools.suckless.org/farbfeld) image and publishes it on the
       +root window:
       +
       +        cc -o stbg stbg.c -lX11
       +        stbg wallpaper.ff
       +        stbg - < wallpaper.ff
       +
       +Once the image is published, every st started afterwards tiles it across
       +its window. Re-running `stbg` with a new image updates all running st
       +instances immediately; there is no need to restart or reload them.
        
        Pseudo Transparency
        -------------------
        
       -The variable `pseudotransparency` enables functionality which fixes the 
       -coordinates of the background image to the screen origin. This emulates the 
       -effect of transparency without the need for an *X composite manager*.
       +The variable `pseudotransparency` in `config.def.h` fixes the
       +coordinates of the background image to the screen origin. This emulates
       +the effect of transparency without the need for an *X composite
       +manager*.
        
       -*Hint*: With the use of [farbfeld utilities](http://zzo38computer.org/fossil/farbfeld.ui/) 
       -effects can be applied to the desktop background in an automated fashion. 
       -Pictured below is an example of the result of a darken and blur operation 
       -invoked with the following command:
       +*Hint*: Because image processing is done outside of st, effects can be
       +applied to the desktop background in an automated fashion with external
       +tools. Pictured below is an example of the result of a darken and blur
       +operation using [farbfeld utilities](https://web.archive.org/web/20211205013032/http://zzo38computer.org/fossil/farbfeld.ui/index):
        
       -        jpg2ff < wallpaper.jpg | ff-border e 50 | ff-bright rgba 0 0.5 1 | ff-blur 50 15 > st_wallpaper.ff
       +        jpg2ff < wallpaper.jpg | ff-border e 50 | ff-bright rgba 0 0.5 1 | ff-blur 50 15 | stbg -
        
        ![Screenshot](pseudo-transparency.png)
        
       -Download
       ---------
       +Legacy versions
       +---------------
       +
       +Older versions of this patch loaded the farbfeld image directly inside
       +st. They still work but have several shortcomings that are fixed by the
       +0.9.3 version above:
       +
       +* **Per-instance decoding.** Each st instance read and decoded the
       +  image file from disk and created its own copy of the pixmap. With
       +  several terminals open the same image was held in memory multiple
       +  times.
       +* **No live updates.** The background was only loaded at startup.
       +  Updating it required the *signal reload* hack: sending a `USR1`
       +  signal to every st process (`pidof st | xargs kill -s USR1`) after
       +  regenerating the image file, assuming the extra
       +  `st-background-image-signal-reload-0.8.5.diff` patch had been
       +  applied on top.
       +* **Recompilation to change the image.** The image path was baked in
       +  at build time via the `bgfile` variable in `config.h`.
       +* **File format parsing inside the terminal.** The farbfeld loading
       +  code, including `arpa/inet` byte-swapping, lived in `x.c`, tying the
       +  terminal to a single image format.
       +
       +Legacy downloads:
        
        * [st-background-image-0.8.4.diff](st-background-image-0.8.4.diff)
        * [st-background-image-0.8.5.diff](st-background-image-0.8.5.diff)
       -
       -Signal Reloading
       -----------------
       -
       -Apply the following patch on top of the previous to enable reloading the
       -background image when a USR1 signal occurs:
       -
        * [st-background-image-signal-reload-0.8.5.diff](st-background-image-signal-reload-0.8.5.diff)
        
       -If you use the [xresources with signal reloading](//st.suckless.org/patches/xresources-with-reload-signal)
       -patch ignore the patch above and simply add these two lines to the beginning of
       -the `reload` function located in the file `x.c`:
       -
       -        XFreeGC(xw.dpy, xw.bggc);
       -        bginit();
       -
       -*Hint*: You can send a USR1 signal to all st processes with the following
       -command:
       -
       -        pidof st | xargs kill -s USR1
       -
        Authors
        -------
       -* Matthias Schoth - <mschoth@gmail.com>
        
       +* Matthias Schoth - <mschoth@gmail.com>
   DIR 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
       @@ -0,0 +1,391 @@
       +From 5f070227058901afd7525ffe6363c93faf163ef0 Mon Sep 17 00:00:00 2001
       +From: Matthias Schoth <mschoth@gmail.com>
       +Date: Fri, 31 Jul 2026 23:50:51 +0200
       +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
       +_ST_BACKGROUND_IMAGE root window property. st watches the root window
       +for property changes, so re-running the stbg setter updates all running
       +st instances immediately.
       +
       +The pixmap is shared between all instances and no composite manager is
       +required. A pseudotransparency config option fixes the tile coordinates
       +to the screen origin so the terminal backgrounds line up with the
       +wallpaper behind the window.
       +
       +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
       +---
       + config.def.h |   6 +++
       + stbg.c       | 122 ++++++++++++++++++++++++++++++++++++++++++++++++
       + x.c          | 128 +++++++++++++++++++++++++++++++++++++++++++++++----
       + 3 files changed, 246 insertions(+), 10 deletions(-)
       + create mode 100644 stbg.c
       +
       +diff --git a/config.def.h b/config.def.h
       +index 2cd740a..3d98c87 100644
       +--- a/config.def.h
       ++++ b/config.def.h
       +@@ -8,6 +8,12 @@
       + static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
       + static int borderpx = 2;
       + 
       ++/*
       ++ * pseudo transparency fixes coordinates to the screen origin
       ++ * requires _ST_BACKGROUND_IMAGE root window property (set via stbg)
       ++ */
       ++static const int pseudotransparency = 0;
       ++
       + /*
       +  * What program is execed by st depends of these precedence rules:
       +  * 1: program passed with -e
       +diff --git a/stbg.c b/stbg.c
       +new file mode 100644
       +index 0000000..80bc4cf
       +--- /dev/null
       ++++ b/stbg.c
       +@@ -0,0 +1,122 @@
       ++/* See LICENSE file for copyright and license details. */
       ++/* Build: cc -o stbg stbg.c -lX11 */
       ++#include <errno.h>
       ++#include <stdarg.h>
       ++#include <stdio.h>
       ++#include <stdlib.h>
       ++#include <stdint.h>
       ++#include <string.h>
       ++#include <arpa/inet.h>
       ++#include <X11/Xlib.h>
       ++#include <X11/Xutil.h>
       ++#include <X11/Xatom.h>
       ++
       ++static char *argv0;
       ++
       ++static void
       ++die(const char *errstr, ...)
       ++{
       ++        va_list ap;
       ++
       ++        fprintf(stderr, "%s: ", argv0);
       ++        va_start(ap, errstr);
       ++        vfprintf(stderr, errstr, ap);
       ++        va_end(ap);
       ++        exit(1);
       ++}
       ++
       ++static void
       ++usage(void)
       ++{
       ++        die("usage: %s file.ff\n       %s - < file.ff\n", argv0, argv0);
       ++}
       ++
       ++int
       ++main(int argc, char *argv[])
       ++{
       ++        uint32_t i, hdr[4], w, h, size;
       ++        uint64_t *data;
       ++        uint32_t *data32;
       ++        FILE *f;
       ++        int fromstdin;
       ++        Pixmap pmap;
       ++        Display *dpy;
       ++        Window root;
       ++        int scr;
       ++        Atom atom_stbg;
       ++        GC gc;
       ++        XGCValues gcvalues;
       ++        XImage *xi;
       ++
       ++        argv0 = argv[0];
       ++
       ++        if (argc != 2)
       ++                usage();
       ++
       ++        if (!(dpy = XOpenDisplay(NULL)))
       ++                die("can't open display\n");
       ++
       ++        scr = DefaultScreen(dpy);
       ++        root = RootWindow(dpy, scr);
       ++
       ++        fromstdin = !strcmp(argv[1], "-");
       ++        f = fromstdin ? stdin : fopen(argv[1], "rb");
       ++        if (!fromstdin && !f)
       ++                die("can't open %s\n", argv[1]);
       ++
       ++        if (fread(hdr, sizeof(*hdr), 4, f) != 4)
       ++                die("fread: unexpected end of file\n");
       ++
       ++        if (memcmp("farbfeld", hdr, sizeof("farbfeld") - 1))
       ++                die("invalid farbfeld magic\n");
       ++
       ++        w = ntohl(hdr[2]);
       ++        h = ntohl(hdr[3]);
       ++        if (w == 0 || h == 0)
       ++                die("invalid farbfeld dimensions\n");
       ++        size = w * h;
       ++
       ++        data = malloc(size * sizeof(uint64_t));
       ++        if (!data)
       ++                die("malloc: %s\n", strerror(errno));
       ++
       ++        if (fread(data, sizeof(uint64_t), size, f) != size)
       ++                die("fread: unexpected end of file\n");
       ++        if (!fromstdin)
       ++                fclose(f);
       ++
       ++        data32 = malloc(size * sizeof(uint32_t));
       ++        if (!data32)
       ++                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;
       ++        }
       ++        free(data);
       ++
       ++        xi = XCreateImage(dpy, DefaultVisual(dpy, scr),
       ++                          DefaultDepth(dpy, scr), ZPixmap, 0,
       ++                          (char *)data32, w, h, 32, w * 4);
       ++
       ++        pmap = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, scr));
       ++
       ++        memset(&gcvalues, 0, sizeof(gcvalues));
       ++        gcvalues.graphics_exposures = False;
       ++        gc = XCreateGC(dpy, pmap, GCGraphicsExposures, &gcvalues);
       ++
       ++        XPutImage(dpy, pmap, gc, xi, 0, 0, 0, 0, w, h);
       ++        XDestroyImage(xi);
       ++        XFreeGC(dpy, gc);
       ++
       ++        atom_stbg = XInternAtom(dpy, "_ST_BACKGROUND_IMAGE", False);
       ++        XChangeProperty(dpy, root, atom_stbg, XA_PIXMAP, 32,
       ++                        PropModeReplace, (unsigned char *)&pmap, 1);
       ++        XSetCloseDownMode(dpy, RetainPermanent);
       ++
       ++        XSync(dpy, False);
       ++
       ++        return 0;
       ++}
       +diff --git a/x.c b/x.c
       +index d73152b..c0ec757 100644
       +--- a/x.c
       ++++ b/x.c
       +@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
       + typedef struct {
       +         int tw, th; /* tty width and height */
       +         int w, h; /* window width and height */
       ++        int x, y; /* window location */
       +         int ch; /* char height */
       +         int cw; /* char width  */
       +         int mode; /* window state/mode flags */
       +@@ -101,6 +102,9 @@ typedef struct {
       +                 XVaNestedList spotlist;
       +         } ime;
       +         Draw draw;
       ++        GC bggc;          /* Graphics Context for background */
       ++        Atom stbg_atom;   /* _ST_BACKGROUND_IMAGE atom */
       ++        Atom netwmstate_atom; /* _NET_WM_STATE atom */
       +         Visual *vis;
       +         XSetWindowAttributes attrs;
       +         int scr;
       +@@ -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);
       ++static void updatexy(void);
       ++static void bginit(void);
       ++static Pixmap bginitfromroot(void);
       + static void cresize(int, int);
       + static void xresize(int, int);
       + static void xhints(void);
       +@@ -515,6 +522,18 @@ propnotify(XEvent *e)
       +                          xpev->atom == clipboard)) {
       +                 selnotify(e);
       +         }
       ++
       ++        if (xpev->atom == xw.stbg_atom) {
       ++                bginit();
       ++                redraw();
       ++                return;
       ++        }
       ++
       ++        if (pseudotransparency &&
       ++            e->xproperty.atom == xw.netwmstate_atom) {
       ++                updatexy();
       ++                redraw();
       ++        }
       + }
       + 
       + void
       +@@ -545,7 +564,8 @@ selnotify(XEvent *e)
       +                         return;
       +                 }
       + 
       +-                if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
       ++                if (e->type == PropertyNotify && nitems == 0 && rem == 0 &&
       ++                    !pseudotransparency) {
       +                         /*
       +                          * If there is some PropertyNotify with no data, then
       +                          * this is the signal of the selection owner that all
       +@@ -563,9 +583,11 @@ selnotify(XEvent *e)
       +                          * when the selection owner does send us the next
       +                          * chunk of data.
       +                          */
       +-                        MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
       +-                        XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
       ++                        if (!pseudotransparency) {
       ++                                MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
       ++                                XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
       +                                         &xw.attrs);
       ++                        }
       + 
       +                         /*
       +                          * Deleting the property is the transfer start signal.
       +@@ -851,9 +873,9 @@ 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 (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 +1264,78 @@ xinit(int cols, int rows)
       +                 xsel.xtarget = XA_STRING;
       + }
       + 
       ++void
       ++updatexy(void)
       ++{
       ++        Window child;
       ++        XTranslateCoordinates(xw.dpy, xw.win, DefaultRootWindow(xw.dpy), 0, 0,
       ++                              &win.x, &win.y, &child);
       ++}
       ++
       ++/*
       ++ * initialize background image from root window _ST_BACKGROUND_IMAGE
       ++ */
       ++static Pixmap
       ++bginitfromroot(void)
       ++{
       ++        Atom prop, atom_type;
       ++        int format;
       ++        unsigned long nitems, after;
       ++        unsigned char *data;
       ++        Pixmap pmap;
       ++
       ++        prop = XInternAtom(xw.dpy, "_ST_BACKGROUND_IMAGE", True);
       ++        if (prop == None)
       ++                return None;
       ++
       ++        if (XGetWindowProperty(xw.dpy, DefaultRootWindow(xw.dpy), prop,
       ++                               0, 1, False, AnyPropertyType, &atom_type,
       ++                               &format, &nitems, &after, &data) != Success)
       ++                return None;
       ++
       ++        if (atom_type != XA_PIXMAP || format != 32 || nitems < 1) {
       ++                XFree(data);
       ++                return None;
       ++        }
       ++
       ++        pmap = *(Pixmap *)data;
       ++        XFree(data);
       ++
       ++        XSetTile(xw.dpy, xw.bggc, pmap);
       ++        XSetFillStyle(xw.dpy, xw.bggc, FillTiled);
       ++        return pmap;
       ++}
       ++
       ++void
       ++bginit(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) {
       ++                updatexy();
       ++                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);
       ++}
       ++
       + int
       + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
       + {
       +@@ -1482,7 +1576,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. */
       +-        XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
       ++        if (bg == &dc.col[defaultbg])
       ++                xclear(winx, winy, winx + width, winy + win.ch);
       ++        else
       ++                XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
       + 
       +         /* Set the clip region because Xft is sometimes dirty. */
       +         r.x = 0;
       +@@ -1914,9 +2011,17 @@ cmessage(XEvent *e)
       + void
       + resize(XEvent *e)
       + {
       +-        if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
       +-                return;
       +-
       ++        if (pseudotransparency) {
       ++                if (e->xconfigure.width == win.w &&
       ++                    e->xconfigure.height == win.h &&
       ++                    e->xconfigure.x == win.x && e->xconfigure.y == win.y)
       ++                        return;
       ++                updatexy();
       ++        } else {
       ++                if (e->xconfigure.width == win.w &&
       ++                    e->xconfigure.height == win.h)
       ++                        return;
       ++        }
       +         cresize(e->xconfigure.width, e->xconfigure.height);
       + }
       + 
       +@@ -1947,6 +2052,8 @@ run(void)
       +         } while (ev.type != MapNotify);
       + 
       +         ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
       ++        if (pseudotransparency)
       ++                updatexy();
       +         cresize(w, h);
       + 
       +         for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
       +@@ -2100,6 +2207,7 @@ run:
       +         rows = MAX(rows, 1);
       +         tnew(cols, rows);
       +         xinit(cols, rows);
       ++        bginit();
       +         xsetenv();
       +         selinit();
       +         run();
       +-- 
       +2.55.0
       +