commit b2bcc0bde65b019274bfbd64d1a1a290ba6dfb62
parent bc7b57379bce0e7f7b3a974724e0f1a1c754a190
Author: Matthias Schoth <mschoth@gmail.com>
Date: Sun, 8 Nov 2026 14:53:53 +0100
[st][patches][background_image] Fix double paste with pseudotransparency
pseudotransparency keeps PropertyChangeMask enabled on the st window to
track _NET_WM_STATE. This also delivered ordinary selection PropertyNotify
events to selnotify(), so a paste was processed twice (an extra empty
bracketed paste).
Solution: Track the property of an in-progress INCR transfer and only route
selection PropertyNotify events while that transfer is active.
Behavior without pseudotransparency is unchanged.
Diffstat:
1 file changed, 62 insertions(+), 30 deletions(-)
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
@@ -1,6 +1,6 @@
-From c2ae9bfcb131b13435398922b1baf88c87df8908 Mon Sep 17 00:00:00 2001
+From d910857f5b63dbfca4299a0daa7ce3155c880543 Mon Sep 17 00:00:00 2001
From: Matthias Schoth <mschoth@gmail.com>
-Date: Sat, 31 Oct 2026 22:41:01 +0100
+Date: Sun, 8 Nov 2026 14:15:23 +0100
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
@@ -17,19 +17,13 @@ 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
-
-A new image is decoded and published before the previous owner is
-released, so a failed read leaves the current background untouched.
-Only the current pixmap is kept alive; replacing the background kills
-the client that owns the previous one, so periodic updates do not
-accumulate X clients or server-side image memory.
---
config.def.h | 6 ++
st.c | 2 +-
st.h | 1 +
stbg.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++
- x.c | 127 +++++++++++++++++++++++++++++++++---
- 5 files changed, 305 insertions(+), 10 deletions(-)
+ x.c | 147 ++++++++++++++++++++++++++++++++++++++----
+ 5 files changed, 322 insertions(+), 13 deletions(-)
create mode 100644 stbg.c
diff --git a/config.def.h b/config.def.h
@@ -260,7 +254,7 @@ index 0000000..ed9c2fa
+ return 0;
+}
diff --git a/x.c b/x.c
-index d73152b..bee9d35 100644
+index d73152b..dad569f 100644
--- a/x.c
+++ b/x.c
@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
@@ -281,7 +275,15 @@ index d73152b..bee9d35 100644
Visual *vis;
XSetWindowAttributes attrs;
int scr;
-@@ -151,6 +155,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
+@@ -111,6 +115,7 @@ typedef struct {
+
+ typedef struct {
+ Atom xtarget;
++ Atom incrprop; /* property of an in-progress INCR transfer */
+ char *primary, *clipboard;
+ struct timespec tclick1;
+ struct timespec tclick2;
+@@ -151,6 +156,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
static void ximdestroy(XIM, XPointer, XPointer);
static int xicdestroy(XIC, XPointer, XPointer);
static void xinit(int, int);
@@ -291,7 +293,7 @@ index d73152b..bee9d35 100644
static void cresize(int, int);
static void xresize(int, int);
static void xhints(void);
-@@ -220,6 +227,7 @@ static DC dc;
+@@ -220,6 +228,7 @@ static DC dc;
static XWindow xw;
static XSelection xsel;
static TermWindow win;
@@ -299,8 +301,21 @@ index d73152b..bee9d35 100644
/* Font Ring Cache */
enum {
-@@ -515,6 +523,17 @@ propnotify(XEvent *e)
- xpev->atom == clipboard)) {
+@@ -510,11 +519,29 @@ propnotify(XEvent *e)
+ Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+
+ xpev = &e->xproperty;
++ /*
++ * propnotify is only used for incremental transfers. With
++ * pseudotransparency PropertyChangeMask is kept enabled all the
++ * time, so ordinary selection property changes arrive here too;
++ * processing them would paste the selection a second time.
++ */
+ if (xpev->state == PropertyNewValue &&
+ (xpev->atom == XA_PRIMARY ||
+- xpev->atom == clipboard)) {
++ xpev->atom == clipboard) &&
++ xsel.incrprop == xpev->atom) {
selnotify(e);
}
+
@@ -317,22 +332,39 @@ index d73152b..bee9d35 100644
}
void
-@@ -545,7 +564,8 @@ selnotify(XEvent *e)
- return;
+@@ -536,6 +563,9 @@ selnotify(XEvent *e)
+ if (property == None)
+ return;
+
++ if (e->type == SelectionNotify)
++ xsel.incrprop = None;
++
+ do {
+ if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
+ BUFSIZ/4, False, AnyPropertyType,
+@@ -552,9 +582,12 @@ selnotify(XEvent *e)
+ * data has been transferred. We won't need to receive
+ * PropertyNotify events anymore.
+ */
+- MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
+- XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
+- &xw.attrs);
++ xsel.incrprop = None;
++ if (!pseudotransparency) {
++ MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
++ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
++ &xw.attrs);
++ }
}
-- 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)
+ if (type == incratom) {
+@@ -563,9 +596,12 @@ 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,
++ xsel.incrprop = property;
+ if (!pseudotransparency) {
+ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
@@ -341,7 +373,7 @@ index d73152b..bee9d35 100644
/*
* Deleting the property is the transfer start signal.
-@@ -851,9 +873,9 @@ xsetcolorname(int x, const char *name)
+@@ -851,9 +887,9 @@ xsetcolorname(int x, const char *name)
void
xclear(int x1, int y1, int x2, int y2)
{
@@ -354,7 +386,7 @@ index d73152b..bee9d35 100644
}
void
-@@ -1242,6 +1264,77 @@ xinit(int cols, int rows)
+@@ -1242,6 +1278,77 @@ xinit(int cols, int rows)
xsel.xtarget = XA_STRING;
}
@@ -432,7 +464,7 @@ index d73152b..bee9d35 100644
int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
-@@ -1482,7 +1575,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+@@ -1482,7 +1589,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. */
@@ -444,7 +476,7 @@ index d73152b..bee9d35 100644
/* Set the clip region because Xft is sometimes dirty. */
r.x = 0;
-@@ -1651,6 +1747,12 @@ xsettitle(char *p)
+@@ -1651,6 +1761,12 @@ xsettitle(char *p)
int
xstartdraw(void)
{
@@ -457,7 +489,7 @@ index d73152b..bee9d35 100644
return IS_SET(MODE_VISIBLE);
}
-@@ -1914,9 +2016,15 @@ cmessage(XEvent *e)
+@@ -1914,9 +2030,15 @@ cmessage(XEvent *e)
void
resize(XEvent *e)
{
@@ -475,7 +507,7 @@ index d73152b..bee9d35 100644
cresize(e->xconfigure.width, e->xconfigure.height);
}
-@@ -2100,6 +2208,7 @@ run:
+@@ -2100,6 +2222,7 @@ run:
rows = MAX(rows, 1);
tnew(cols, rows);
xinit(cols, rows);