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