sites

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

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


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