sites

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

commit a7d0eec3224fe452ebbbd0d832a95c9b5240fe5b
parent 74e79f4d45fca3c8a802ca44cb0daebd225262c8
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:
Mst.suckless.org/patches/background_image/index.md | 93+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Ast.suckless.org/patches/background_image/st-background-image-0.9.3.diff | 391+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 447 insertions(+), 37 deletions(-)

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> 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 +