[wiki] [sites] [st][patches][background_image] Fix double paste with pseudotransparency || Matthias Schoth

From: git=suckless.org via suckless wiki list <wiki_at_suckless.org>
Date: Sun, 08 Nov 2026 15:02:41 +0100

commit b2bcc0bde65b019274bfbd64d1a1a290ba6dfb62
Author: Matthias Schoth <mschoth_at_gmail.com>
Date: Sun Nov 8 14:53:53 2026 +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.

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
index 4478fcd4..8f28de88 100644
--- 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
_at_@ -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_at_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
_at_@ -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
_at_@ -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
 _at_@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
_at_@ -281,7 +275,15 @@ index d73152b..bee9d35 100644
          Visual *vis;
          XSetWindowAttributes attrs;
          int scr;
-_at_@ -151,6 +155,9 @@ static void ximinstantiate(Display *, XPointer, XPointer);
+_at_@ -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;
+_at_@ -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);
_at_@ -291,7 +293,7 @@ index d73152b..bee9d35 100644
  static void cresize(int, int);
  static void xresize(int, int);
  static void xhints(void);
-_at_@ -220,6 +227,7 @@ static DC dc;
+_at_@ -220,6 +228,7 @@ static DC dc;
  static XWindow xw;
  static XSelection xsel;
  static TermWindow win;
_at_@ -299,8 +301,21 @@ index d73152b..bee9d35 100644
  
  /* Font Ring Cache */
  enum {
-_at_@ -515,6 +523,17 @@ propnotify(XEvent *e)
- xpev->atom == clipboard)) {
+_at_@ -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);
          }
 +
_at_@ -317,22 +332,39 @@ index d73152b..bee9d35 100644
  }
  
  void
-_at_@ -545,7 +564,8 @@ selnotify(XEvent *e)
- return;
+_at_@ -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,
+_at_@ -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
-_at_@ -563,9 +583,11 @@ selnotify(XEvent *e)
+ if (type == incratom) {
+_at_@ -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,
_at_@ -341,7 +373,7 @@ index d73152b..bee9d35 100644
  
                          /*
                           * Deleting the property is the transfer start signal.
-_at_@ -851,9 +873,9 @@ xsetcolorname(int x, const char *name)
+_at_@ -851,9 +887,9 @@ xsetcolorname(int x, const char *name)
  void
  xclear(int x1, int y1, int x2, int y2)
  {
_at_@ -354,7 +386,7 @@ index d73152b..bee9d35 100644
  }
  
  void
-_at_@ -1242,6 +1264,77 @@ xinit(int cols, int rows)
+_at_@ -1242,6 +1278,77 @@ xinit(int cols, int rows)
                  xsel.xtarget = XA_STRING;
  }
  
_at_@ -432,7 +464,7 @@ index d73152b..bee9d35 100644
  int
  xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
  {
-_at_@ -1482,7 +1575,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+_at_@ -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. */
_at_@ -444,7 +476,7 @@ index d73152b..bee9d35 100644
  
          /* Set the clip region because Xft is sometimes dirty. */
          r.x = 0;
-_at_@ -1651,6 +1747,12 @@ xsettitle(char *p)
+_at_@ -1651,6 +1761,12 @@ xsettitle(char *p)
  int
  xstartdraw(void)
  {
_at_@ -457,7 +489,7 @@ index d73152b..bee9d35 100644
          return IS_SET(MODE_VISIBLE);
  }
  
-_at_@ -1914,9 +2016,15 @@ cmessage(XEvent *e)
+_at_@ -1914,9 +2030,15 @@ cmessage(XEvent *e)
  void
  resize(XEvent *e)
  {
_at_@ -475,7 +507,7 @@ index d73152b..bee9d35 100644
          cresize(e->xconfigure.width, e->xconfigure.height);
  }
  
-_at_@ -2100,6 +2208,7 @@ run:
+_at_@ -2100,6 +2222,7 @@ run:
          rows = MAX(rows, 1);
          tnew(cols, rows);
          xinit(cols, rows);
Received on Sun Nov 08 2026 - 15:02:41 CET

This archive was generated by hypermail 2.3.0 : Sun Nov 08 2026 - 15:12:54 CET