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