commit b8d567921ea57e1d1089b8e0c14aff77bce1d93e
Author: Tim Keller <tjkeller.xyz>
Date:   Sat Jan 11 15:03:31 2025 -0600

    add drag and drop patch for st

diff --git a/st.suckless.org/patches/drag-n-drop/index.md 
b/st.suckless.org/patches/drag-n-drop/index.md
new file mode 100644
index 00000000..84ac6e17
--- /dev/null
+++ b/st.suckless.org/patches/drag-n-drop/index.md
@@ -0,0 +1,24 @@
+XDND drag-n-drop
+================
+This patch adds
+[XDND Drag-and-Drop](https://www.freedesktop.org/wiki/Specifications/XDND/)
+support for st.
+
+Description
+-----------
+Dragging a file onto the st window from another XDND enabled window, such as a
+graphical file manager, will insert the file path at your cursor. This behavior
+is common in other modern terminal emulators. Multiple files are supported at
+once.
+
+Special characters in the file path (e.g. `space`, `&`, `'`, etc.) are also
+escaped using the `\` character. The full list of escaped characters are stored
+as a string `xdndescchar` in `config.h`.
+
+Download
+--------
+* [st-drag-n-drop-0.9.2.diff](st-drag-n-drop-0.9.2.diff)
+
+Authors
+-------
+* Tim Keller - <t...@tjkeller.xyz>
diff --git a/st.suckless.org/patches/drag-n-drop/st-drag-n-drop-0.9.2.diff 
b/st.suckless.org/patches/drag-n-drop/st-drag-n-drop-0.9.2.diff
new file mode 100644
index 00000000..f853849d
--- /dev/null
+++ b/st.suckless.org/patches/drag-n-drop/st-drag-n-drop-0.9.2.diff
@@ -0,0 +1,343 @@
+diff --git a/config.def.h b/config.def.h
+index 2cd740a..3045d0a 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -93,6 +93,13 @@ char *termname = "st-256color";
+  */
+ unsigned int tabspaces = 8;
+ 
++/*
++ * drag and drop escape characters
++ *
++ * this will add a '\' before any characters specified in the string.
++ */
++char *xdndescchar = " !\"#$&'()*;<>?[\]^`{|}~";
++
+ /* Terminal colors (16 first used in escape sequence) */
+ static const char *colorname[] = {
+       /* 8 normal colors */
+diff --git a/st.h b/st.h
+index fd3b0d8..62c7405 100644
+--- a/st.h
++++ b/st.h
+@@ -20,6 +20,10 @@
+ #define TRUECOLOR(r,g,b)      (1 << 24 | (r) << 16 | (g) << 8 | (b))
+ #define IS_TRUECOL(x)         (1 << 24 & (x))
+ 
++#define HEX_TO_INT(c)         ((c) >= '0' && (c) <= '9' ? (c) - '0' : \
++                              (c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \
++                              (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : -1)
++
+ enum glyph_attribute {
+       ATTR_NULL       = 0,
+       ATTR_BOLD       = 1 << 0,
+diff --git a/x.c b/x.c
+index d73152b..a152ea8 100644
+--- a/x.c
++++ b/x.c
+@@ -94,6 +94,12 @@ typedef struct {
+       Drawable buf;
+       GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
+       Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
++      Atom XdndTypeList, XdndSelection, XdndEnter, XdndPosition, XdndStatus,
++           XdndLeave, XdndDrop, XdndFinished, XdndActionCopy, XdndActionMove,
++           XdndActionLink, XdndActionAsk, XdndActionPrivate, XtextUriList,
++           XtextPlain, XdndAware;
++      int64_t XdndSourceWin, XdndSourceVersion;
++      int32_t XdndSourceFormat;
+       struct {
+               XIM xim;
+               XIC xic;
+@@ -169,6 +175,9 @@ static void visibility(XEvent *);
+ static void unmap(XEvent *);
+ static void kpress(XEvent *);
+ static void cmessage(XEvent *);
++static void xdndenter(XEvent *);
++static void xdndpos(XEvent *);
++static void xdnddrop(XEvent *);
+ static void resize(XEvent *);
+ static void focus(XEvent *);
+ static uint buttonmask(uint);
+@@ -178,6 +187,8 @@ static void bpress(XEvent *);
+ static void bmotion(XEvent *);
+ static void propnotify(XEvent *);
+ static void selnotify(XEvent *);
++static void xdndsel(XEvent *);
++static void xdndpastedata(char *);
+ static void selclear_(XEvent *);
+ static void selrequest(XEvent *);
+ static void setsel(char *, Time);
+@@ -220,6 +231,7 @@ static DC dc;
+ static XWindow xw;
+ static XSelection xsel;
+ static TermWindow win;
++const char XdndVersion = 5;
+ 
+ /* Font Ring Cache */
+ enum {
+@@ -536,6 +548,11 @@ selnotify(XEvent *e)
+       if (property == None)
+               return;
+ 
++      if (property == xw.XdndSelection) {
++              xdndsel(e);
++              return;
++      }
++
+       do {
+               if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
+                                       BUFSIZ/4, False, AnyPropertyType,
+@@ -604,6 +621,93 @@ selnotify(XEvent *e)
+       XDeleteProperty(xw.dpy, xw.win, (int)property);
+ }
+ 
++void
++xdndsel(XEvent *e)
++{
++      char* data;
++      unsigned long result;
++
++      Atom actualType;
++      int32_t actualFormat;
++      unsigned long bytesAfter;
++      XEvent reply = { ClientMessage };
++
++      reply.xclient.window = xw.XdndSourceWin;
++      reply.xclient.format = 32;
++      reply.xclient.data.l[0] = (long) xw.win;
++      reply.xclient.data.l[2] = 0;
++      reply.xclient.data.l[3] = 0;
++
++      XGetWindowProperty((Display*) xw.dpy, e->xselection.requestor,
++                      e->xselection.property, 0, LONG_MAX, False,
++                      e->xselection.target, &actualType, &actualFormat, 
&result,
++                      &bytesAfter, (unsigned char**) &data);
++
++      if (result == 0)
++              return;
++
++      if (data) {
++              xdndpastedata(data);
++              XFree(data);
++      }
++
++      if (xw.XdndSourceVersion >= 2) {
++              reply.xclient.message_type = xw.XdndFinished;
++              reply.xclient.data.l[1] = result;
++              reply.xclient.data.l[2] = xw.XdndActionCopy;
++
++              XSendEvent((Display*) xw.dpy, xw.XdndSourceWin, False, 
NoEventMask,
++                              &reply);
++              XFlush((Display*) xw.dpy);
++      }
++}
++
++int
++xdndurldecode(char *src, char *dest)
++{
++      char c;
++      int i = 0;
++
++      while (*src) {
++              if (*src == '%' && HEX_TO_INT(src[1]) != -1 && 
HEX_TO_INT(src[2]) != -1) {
++                      /* handle %xx escape sequences in url e.g. %20 == ' ' */
++                      c = (char)((HEX_TO_INT(src[1]) << 4) | 
HEX_TO_INT(src[2]));
++                      src += 3;
++              } else {
++                      c = *src++;
++              }
++              if (strchr(xdndescchar, c) != NULL) {
++                      *dest++ = '\';
++                      i++;
++              }
++              *dest++ = c;
++              i++;
++      }
++      *dest++ = ' ';
++      *dest = '++     return i + 1;
++}
++
++void
++xdndpastedata(char *data)
++{
++      char *pastedata, *t;
++      int i = 0;
++
++      pastedata = (char *)malloc(strlen(data) * 2 + 1);
++      *pastedata = '++
++      t = strtok(data, "
 ");
++      while(t != NULL) {
++              t += 7; /* remove 'file://' prefix */
++              i += xdndurldecode(t, pastedata + i);
++              t = strtok(NULL, "
 ");
++      }
++
++      xsetsel(pastedata);
++      selpaste(0);
++}
++
+ void
+ xclipcopy(void)
+ {
+@@ -1227,6 +1331,26 @@ xinit(int cols, int rows)
+       XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
+                       PropModeReplace, (uchar *)&thispid, 1);
+ 
++      /* Xdnd setup */
++      xw.XdndTypeList = XInternAtom(xw.dpy, "XdndTypeList", 0);
++      xw.XdndSelection = XInternAtom(xw.dpy, "XdndSelection", 0);
++      xw.XdndEnter = XInternAtom(xw.dpy, "XdndEnter", 0);
++      xw.XdndPosition = XInternAtom(xw.dpy, "XdndPosition", 0);
++      xw.XdndStatus = XInternAtom(xw.dpy, "XdndStatus", 0);
++      xw.XdndLeave = XInternAtom(xw.dpy, "XdndLeave", 0);
++      xw.XdndDrop = XInternAtom(xw.dpy, "XdndDrop", 0);
++      xw.XdndFinished = XInternAtom(xw.dpy, "XdndFinished", 0);
++      xw.XdndActionCopy = XInternAtom(xw.dpy, "XdndActionCopy", 0);
++      xw.XdndActionMove = XInternAtom(xw.dpy, "XdndActionMove", 0);
++      xw.XdndActionLink = XInternAtom(xw.dpy, "XdndActionLink", 0);
++      xw.XdndActionAsk = XInternAtom(xw.dpy, "XdndActionAsk", 0);
++      xw.XdndActionPrivate = XInternAtom(xw.dpy, "XdndActionPrivate", 0);
++      xw.XtextUriList = XInternAtom((Display*) xw.dpy, "text/uri-list", 0);
++      xw.XtextPlain = XInternAtom((Display*) xw.dpy, "text/plain", 0);
++      xw.XdndAware = XInternAtom(xw.dpy, "XdndAware", 0);
++      XChangeProperty(xw.dpy, xw.win, xw.XdndAware, 4, 32, PropModeReplace,
++                      &XdndVersion, 1);
++
+       win.mode = MODE_NUMLOCK;
+       resettitle();
+       xhints();
+@@ -1908,6 +2032,132 @@ cmessage(XEvent *e)
+       } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
+               ttyhangup();
+               exit(0);
++      } else if (e->xclient.message_type == xw.XdndEnter) {
++              xw.XdndSourceWin = e->xclient.data.l[0];
++              xw.XdndSourceVersion = e->xclient.data.l[1] >> 24;
++              xw.XdndSourceFormat = None;
++              if (xw.XdndSourceVersion > 5)
++                      return;
++              xdndenter(e);
++      } else if (e->xclient.message_type == xw.XdndPosition
++                      && xw.XdndSourceVersion <= 5) {
++              xdndpos(e);
++      } else if (e->xclient.message_type == xw.XdndDrop
++                      && xw.XdndSourceVersion <= 5) {
++              xdnddrop(e);
++      }
++}
++
++void
++xdndenter(XEvent *e)
++{
++      unsigned long count;
++      Atom* formats;
++      Atom real_formats[6];
++      Bool list;
++      Atom actualType;
++      int32_t actualFormat;
++      unsigned long bytesAfter;
++      unsigned long i;
++
++      list = e->xclient.data.l[1] & 1;
++
++      if (list) {
++              XGetWindowProperty((Display*) xw.dpy,
++                      xw.XdndSourceWin,
++                      xw.XdndTypeList,
++                      0,
++                      LONG_MAX,
++                      False,
++                      4,
++                      &actualType,
++                      &actualFormat,
++                      &count,
++                      &bytesAfter,
++                      (unsigned char**) &formats);
++      } else {
++              count = 0;
++
++              if (e->xclient.data.l[2] != None)
++                      real_formats[count++] = e->xclient.data.l[2];
++              if (e->xclient.data.l[3] != None)
++                      real_formats[count++] = e->xclient.data.l[3];
++              if (e->xclient.data.l[4] != None)
++                      real_formats[count++] = e->xclient.data.l[4];
++
++              formats = real_formats;
++      }
++
++      for (i = 0; i < count; i++) {
++              if (formats[i] == xw.XtextUriList || formats[i] == 
xw.XtextPlain) {
++                      xw.XdndSourceFormat = formats[i];
++                      break;
++              }
++      }
++
++      if (list)
++              XFree(formats);
++}
++
++void
++xdndpos(XEvent *e)
++{
++      const int32_t xabs = (e->xclient.data.l[2] >> 16) & 0xffff;
++      const int32_t yabs = (e->xclient.data.l[2]) & 0xffff;
++      Window dummy;
++      int32_t xpos, ypos;
++      XEvent reply = { ClientMessage };
++
++      reply.xclient.window = xw.XdndSourceWin;
++      reply.xclient.format = 32;
++      reply.xclient.data.l[0] = (long) xw.win;
++      reply.xclient.data.l[2] = 0;
++      reply.xclient.data.l[3] = 0;
++
++      XTranslateCoordinates((Display*) xw.dpy,
++              XDefaultRootWindow((Display*) xw.dpy),
++              (Window) xw.win,
++              xabs, yabs,
++              &xpos, &ypos,
++              &dummy);
++
++      reply.xclient.message_type = xw.XdndStatus;
++
++      if (xw.XdndSourceFormat) {
++              reply.xclient.data.l[1] = 1;
++              if (xw.XdndSourceVersion >= 2)
++                      reply.xclient.data.l[4] = xw.XdndActionCopy;
++      }
++
++      XSendEvent((Display*) xw.dpy, xw.XdndSourceWin, False, NoEventMask,
++                      &reply);
++      XFlush((Display*) xw.dpy);
++}
++
++void
++xdnddrop(XEvent *e)
++{
++      Time time = CurrentTime;
++      XEvent reply = { ClientMessage };
++
++      reply.xclient.window = xw.XdndSourceWin;
++      reply.xclient.format = 32;
++      reply.xclient.data.l[0] = (long) xw.win;
++      reply.xclient.data.l[2] = 0;
++      reply.xclient.data.l[3] = 0;
++
++      if (xw.XdndSourceFormat) {
++              if (xw.XdndSourceVersion >= 1)
++                      time = e->xclient.data.l[2];
++
++              XConvertSelection((Display*) xw.dpy, xw.XdndSelection,
++                              xw.XdndSourceFormat, xw.XdndSelection, (Window) 
xw.win, time);
++      } else if (xw.XdndSourceVersion >= 2) {
++              reply.xclient.message_type = xw.XdndFinished;
++
++              XSendEvent((Display*) xw.dpy, xw.XdndSourceWin,
++                              False, NoEventMask, &reply);
++              XFlush((Display*) xw.dpy);
+       }
+ }
+ 


Reply via email to