sites

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

st-background-image-0.9.3.diff (10388B)


      1 From 5f070227058901afd7525ffe6363c93faf163ef0 Mon Sep 17 00:00:00 2001
      2 From: Matthias Schoth <mschoth@gmail.com>
      3 Date: Fri, 31 Jul 2026 23:50:51 +0200
      4 Subject: [PATCH] Add background image support via root window pixmap
      5 
      6 Add a background image to st by tiling a server-side pixmap set as the
      7 _ST_BACKGROUND_IMAGE root window property. st watches the root window
      8 for property changes, so re-running the stbg setter updates all running
      9 st instances immediately.
     10 
     11 The pixmap is shared between all instances and no composite manager is
     12 required. A pseudotransparency config option fixes the tile coordinates
     13 to the screen origin so the terminal backgrounds line up with the
     14 wallpaper behind the window.
     15 
     16 stbg reads a farbfeld image from a file or stdin (using -) and sets the
     17 property. It is not built by default; compile separately with:
     18 
     19     cc -o stbg stbg.c -lX11
     20 ---
     21  config.def.h |   6 +++
     22  stbg.c       | 122 ++++++++++++++++++++++++++++++++++++++++++++++++
     23  x.c          | 128 +++++++++++++++++++++++++++++++++++++++++++++++----
     24  3 files changed, 246 insertions(+), 10 deletions(-)
     25  create mode 100644 stbg.c
     26 
     27 diff --git a/config.def.h b/config.def.h
     28 index 2cd740a..3d98c87 100644
     29 --- a/config.def.h
     30 +++ b/config.def.h
     31 @@ -8,6 +8,12 @@
     32  static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
     33  static int borderpx = 2;
     34  
     35 +/*
     36 + * pseudo transparency fixes coordinates to the screen origin
     37 + * requires _ST_BACKGROUND_IMAGE root window property (set via stbg)
     38 + */
     39 +static const int pseudotransparency = 0;
     40 +
     41  /*
     42   * What program is execed by st depends of these precedence rules:
     43   * 1: program passed with -e
     44 diff --git a/stbg.c b/stbg.c
     45 new file mode 100644
     46 index 0000000..80bc4cf
     47 --- /dev/null
     48 +++ b/stbg.c
     49 @@ -0,0 +1,122 @@
     50 +/* See LICENSE file for copyright and license details. */
     51 +/* Build: cc -o stbg stbg.c -lX11 */
     52 +#include <errno.h>
     53 +#include <stdarg.h>
     54 +#include <stdio.h>
     55 +#include <stdlib.h>
     56 +#include <stdint.h>
     57 +#include <string.h>
     58 +#include <arpa/inet.h>
     59 +#include <X11/Xlib.h>
     60 +#include <X11/Xutil.h>
     61 +#include <X11/Xatom.h>
     62 +
     63 +static char *argv0;
     64 +
     65 +static void
     66 +die(const char *errstr, ...)
     67 +{
     68 +	va_list ap;
     69 +
     70 +	fprintf(stderr, "%s: ", argv0);
     71 +	va_start(ap, errstr);
     72 +	vfprintf(stderr, errstr, ap);
     73 +	va_end(ap);
     74 +	exit(1);
     75 +}
     76 +
     77 +static void
     78 +usage(void)
     79 +{
     80 +	die("usage: %s file.ff\n       %s - < file.ff\n", argv0, argv0);
     81 +}
     82 +
     83 +int
     84 +main(int argc, char *argv[])
     85 +{
     86 +	uint32_t i, hdr[4], w, h, size;
     87 +	uint64_t *data;
     88 +	uint32_t *data32;
     89 +	FILE *f;
     90 +	int fromstdin;
     91 +	Pixmap pmap;
     92 +	Display *dpy;
     93 +	Window root;
     94 +	int scr;
     95 +	Atom atom_stbg;
     96 +	GC gc;
     97 +	XGCValues gcvalues;
     98 +	XImage *xi;
     99 +
    100 +	argv0 = argv[0];
    101 +
    102 +	if (argc != 2)
    103 +		usage();
    104 +
    105 +	if (!(dpy = XOpenDisplay(NULL)))
    106 +		die("can't open display\n");
    107 +
    108 +	scr = DefaultScreen(dpy);
    109 +	root = RootWindow(dpy, scr);
    110 +
    111 +	fromstdin = !strcmp(argv[1], "-");
    112 +	f = fromstdin ? stdin : fopen(argv[1], "rb");
    113 +	if (!fromstdin && !f)
    114 +		die("can't open %s\n", argv[1]);
    115 +
    116 +	if (fread(hdr, sizeof(*hdr), 4, f) != 4)
    117 +		die("fread: unexpected end of file\n");
    118 +
    119 +	if (memcmp("farbfeld", hdr, sizeof("farbfeld") - 1))
    120 +		die("invalid farbfeld magic\n");
    121 +
    122 +	w = ntohl(hdr[2]);
    123 +	h = ntohl(hdr[3]);
    124 +	if (w == 0 || h == 0)
    125 +		die("invalid farbfeld dimensions\n");
    126 +	size = w * h;
    127 +
    128 +	data = malloc(size * sizeof(uint64_t));
    129 +	if (!data)
    130 +		die("malloc: %s\n", strerror(errno));
    131 +
    132 +	if (fread(data, sizeof(uint64_t), size, f) != size)
    133 +		die("fread: unexpected end of file\n");
    134 +	if (!fromstdin)
    135 +		fclose(f);
    136 +
    137 +	data32 = malloc(size * sizeof(uint32_t));
    138 +	if (!data32)
    139 +		die("malloc: %s\n", strerror(errno));
    140 +
    141 +	for (i = 0; i < size; i++) {
    142 +		uint64_t p = data[i];
    143 +		data32[i] = (p & 0x00000000000000FF) << 16 |
    144 +		            (p & 0x0000000000FF0000) >> 8  |
    145 +		            (p & 0x000000FF00000000) >> 32;
    146 +	}
    147 +	free(data);
    148 +
    149 +	xi = XCreateImage(dpy, DefaultVisual(dpy, scr),
    150 +	                  DefaultDepth(dpy, scr), ZPixmap, 0,
    151 +	                  (char *)data32, w, h, 32, w * 4);
    152 +
    153 +	pmap = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, scr));
    154 +
    155 +	memset(&gcvalues, 0, sizeof(gcvalues));
    156 +	gcvalues.graphics_exposures = False;
    157 +	gc = XCreateGC(dpy, pmap, GCGraphicsExposures, &gcvalues);
    158 +
    159 +	XPutImage(dpy, pmap, gc, xi, 0, 0, 0, 0, w, h);
    160 +	XDestroyImage(xi);
    161 +	XFreeGC(dpy, gc);
    162 +
    163 +	atom_stbg = XInternAtom(dpy, "_ST_BACKGROUND_IMAGE", False);
    164 +	XChangeProperty(dpy, root, atom_stbg, XA_PIXMAP, 32,
    165 +	                PropModeReplace, (unsigned char *)&pmap, 1);
    166 +	XSetCloseDownMode(dpy, RetainPermanent);
    167 +
    168 +	XSync(dpy, False);
    169 +
    170 +	return 0;
    171 +}
    172 diff --git a/x.c b/x.c
    173 index d73152b..c0ec757 100644
    174 --- a/x.c
    175 +++ b/x.c
    176 @@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
    177  typedef struct {
    178  	int tw, th; /* tty width and height */
    179  	int w, h; /* window width and height */
    180 +	int x, y; /* window location */
    181  	int ch; /* char height */
    182  	int cw; /* char width  */
    183  	int mode; /* window state/mode flags */
    184 @@ -101,6 +102,9 @@ typedef struct {
    185  		XVaNestedList spotlist;
    186  	} ime;
    187  	Draw draw;
    188 +	GC bggc;          /* Graphics Context for background */
    189 +	Atom stbg_atom;   /* _ST_BACKGROUND_IMAGE atom */
    190 +	Atom netwmstate_atom; /* _NET_WM_STATE atom */
    191  	Visual *vis;
    192  	XSetWindowAttributes attrs;
    193  	int scr;
    194 @@ -151,6 +155,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
    195  static void ximdestroy(XIM, XPointer, XPointer);
    196  static int xicdestroy(XIC, XPointer, XPointer);
    197  static void xinit(int, int);
    198 +static void updatexy(void);
    199 +static void bginit(void);
    200 +static Pixmap bginitfromroot(void);
    201  static void cresize(int, int);
    202  static void xresize(int, int);
    203  static void xhints(void);
    204 @@ -515,6 +522,18 @@ propnotify(XEvent *e)
    205  			 xpev->atom == clipboard)) {
    206  		selnotify(e);
    207  	}
    208 +
    209 +	if (xpev->atom == xw.stbg_atom) {
    210 +		bginit();
    211 +		redraw();
    212 +		return;
    213 +	}
    214 +
    215 +	if (pseudotransparency &&
    216 +	    e->xproperty.atom == xw.netwmstate_atom) {
    217 +		updatexy();
    218 +		redraw();
    219 +	}
    220  }
    221  
    222  void
    223 @@ -545,7 +564,8 @@ selnotify(XEvent *e)
    224  			return;
    225  		}
    226  
    227 -		if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
    228 +		if (e->type == PropertyNotify && nitems == 0 && rem == 0 &&
    229 +		    !pseudotransparency) {
    230  			/*
    231  			 * If there is some PropertyNotify with no data, then
    232  			 * this is the signal of the selection owner that all
    233 @@ -563,9 +583,11 @@ selnotify(XEvent *e)
    234  			 * when the selection owner does send us the next
    235  			 * chunk of data.
    236  			 */
    237 -			MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
    238 -			XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    239 +			if (!pseudotransparency) {
    240 +				MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
    241 +				XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    242  					&xw.attrs);
    243 +			}
    244  
    245  			/*
    246  			 * Deleting the property is the transfer start signal.
    247 @@ -851,9 +873,9 @@ xsetcolorname(int x, const char *name)
    248  void
    249  xclear(int x1, int y1, int x2, int y2)
    250  {
    251 -	XftDrawRect(xw.draw,
    252 -			&dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
    253 -			x1, y1, x2-x1, y2-y1);
    254 +	if (pseudotransparency)
    255 +		XSetTSOrigin(xw.dpy, xw.bggc, -win.x, -win.y);
    256 +	XFillRectangle(xw.dpy, xw.buf, xw.bggc, x1, y1, x2-x1, y2-y1);
    257  }
    258  
    259  void
    260 @@ -1242,6 +1264,78 @@ xinit(int cols, int rows)
    261  		xsel.xtarget = XA_STRING;
    262  }
    263  
    264 +void
    265 +updatexy(void)
    266 +{
    267 +	Window child;
    268 +	XTranslateCoordinates(xw.dpy, xw.win, DefaultRootWindow(xw.dpy), 0, 0,
    269 +	                      &win.x, &win.y, &child);
    270 +}
    271 +
    272 +/*
    273 + * initialize background image from root window _ST_BACKGROUND_IMAGE
    274 + */
    275 +static Pixmap
    276 +bginitfromroot(void)
    277 +{
    278 +	Atom prop, atom_type;
    279 +	int format;
    280 +	unsigned long nitems, after;
    281 +	unsigned char *data;
    282 +	Pixmap pmap;
    283 +
    284 +	prop = XInternAtom(xw.dpy, "_ST_BACKGROUND_IMAGE", True);
    285 +	if (prop == None)
    286 +		return None;
    287 +
    288 +	if (XGetWindowProperty(xw.dpy, DefaultRootWindow(xw.dpy), prop,
    289 +	                       0, 1, False, AnyPropertyType, &atom_type,
    290 +	                       &format, &nitems, &after, &data) != Success)
    291 +		return None;
    292 +
    293 +	if (atom_type != XA_PIXMAP || format != 32 || nitems < 1) {
    294 +		XFree(data);
    295 +		return None;
    296 +	}
    297 +
    298 +	pmap = *(Pixmap *)data;
    299 +	XFree(data);
    300 +
    301 +	XSetTile(xw.dpy, xw.bggc, pmap);
    302 +	XSetFillStyle(xw.dpy, xw.bggc, FillTiled);
    303 +	return pmap;
    304 +}
    305 +
    306 +void
    307 +bginit(void)
    308 +{
    309 +	XGCValues gcvalues;
    310 +	Pixmap bgpmap;
    311 +
    312 +	if (xw.bggc)
    313 +		XFreeGC(xw.dpy, xw.bggc);
    314 +
    315 +	memset(&gcvalues, 0, sizeof(gcvalues));
    316 +	xw.bggc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues);
    317 +
    318 +	bgpmap = bginitfromroot();
    319 +
    320 +	if (bgpmap == None) {
    321 +		XSetForeground(xw.dpy, xw.bggc, dc.col[defaultbg].pixel);
    322 +		XSetFillStyle(xw.dpy, xw.bggc, FillSolid);
    323 +	}
    324 +
    325 +	if (pseudotransparency) {
    326 +		updatexy();
    327 +		MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
    328 +		XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
    329 +	}
    330 +
    331 +	xw.stbg_atom = XInternAtom(xw.dpy, "_ST_BACKGROUND_IMAGE", False);
    332 +	xw.netwmstate_atom = XInternAtom(xw.dpy, "_NET_WM_STATE", False);
    333 +	XSelectInput(xw.dpy, DefaultRootWindow(xw.dpy), PropertyChangeMask);
    334 +}
    335 +
    336  int
    337  xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
    338  {
    339 @@ -1482,7 +1576,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
    340  		xclear(winx, winy + win.ch, winx + width, win.h);
    341  
    342  	/* Clean up the region we want to draw to. */
    343 -	XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
    344 +	if (bg == &dc.col[defaultbg])
    345 +		xclear(winx, winy, winx + width, winy + win.ch);
    346 +	else
    347 +		XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
    348  
    349  	/* Set the clip region because Xft is sometimes dirty. */
    350  	r.x = 0;
    351 @@ -1914,9 +2011,17 @@ cmessage(XEvent *e)
    352  void
    353  resize(XEvent *e)
    354  {
    355 -	if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
    356 -		return;
    357 -
    358 +	if (pseudotransparency) {
    359 +		if (e->xconfigure.width == win.w &&
    360 +		    e->xconfigure.height == win.h &&
    361 +		    e->xconfigure.x == win.x && e->xconfigure.y == win.y)
    362 +			return;
    363 +		updatexy();
    364 +	} else {
    365 +		if (e->xconfigure.width == win.w &&
    366 +		    e->xconfigure.height == win.h)
    367 +			return;
    368 +	}
    369  	cresize(e->xconfigure.width, e->xconfigure.height);
    370  }
    371  
    372 @@ -1947,6 +2052,8 @@ run(void)
    373  	} while (ev.type != MapNotify);
    374  
    375  	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
    376 +	if (pseudotransparency)
    377 +		updatexy();
    378  	cresize(w, h);
    379  
    380  	for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
    381 @@ -2100,6 +2207,7 @@ run:
    382  	rows = MAX(rows, 1);
    383  	tnew(cols, rows);
    384  	xinit(cols, rows);
    385 +	bginit();
    386  	xsetenv();
    387  	selinit();
    388  	run();
    389 -- 
    390 2.55.0
    391