add drag and drop patch for st - sites - public wiki contents of suckless.org | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
commit b8d567921ea57e1d1089b8e0c14aff77bce1d93e | |
parent 77456c4698f33511b0652c9dfd6f2904b4797584 | |
Author: Tim Keller <tjkeller.xyz> | |
Date: Sat, 11 Jan 2025 15:03:31 -0600 | |
add drag and drop patch for st | |
Diffstat: | |
A st.suckless.org/patches/drag-n-dro… | 24 ++++++++++++++++++++++++ | |
A st.suckless.org/patches/drag-n-dro… | 343 +++++++++++++++++++++++++++… | |
2 files changed, 367 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/st.suckless.org/patches/drag-n-drop/index.md b/st.suckless.org/pat… | |
@@ -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 - <[email protected]> | |
diff --git a/st.suckless.org/patches/drag-n-drop/st-drag-n-drop-0.9.2.diff b/st… | |
@@ -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 : -… | |
++ | |
+ 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, XdndActionMov… | |
++ 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, &re… | |
++ &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, NoEven… | |
++ &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… | |
++ /* handle %xx escape sequences in url e.g. %20 == ' '… | |
++ c = (char)((HEX_TO_INT(src[1]) << 4) | HEX_TO_INT(src… | |
++ src += 3; | |
++ } else { | |
++ c = *src++; | |
++ } | |
++ if (strchr(xdndescchar, c) != NULL) { | |
++ *dest++ = '\\'; | |
++ i++; | |
++ } | |
++ *dest++ = c; | |
++ i++; | |
++ } | |
++ *dest++ = ' '; | |
++ *dest = '\0'; | |
++ return i + 1; | |
++} | |
++ | |
++void | |
++xdndpastedata(char *data) | |
++{ | |
++ char *pastedata, *t; | |
++ int i = 0; | |
++ | |
++ pastedata = (char *)malloc(strlen(data) * 2 + 1); | |
++ *pastedata = '\0'; | |
++ | |
++ t = strtok(data, "\n\r"); | |
++ while(t != NULL) { | |
++ t += 7; /* remove 'file://' prefix */ | |
++ i += xdndurldecode(t, pastedata + i); | |
++ t = strtok(NULL, "\n\r"); | |
++ } | |
++ | |
++ 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.XtextPl… | |
++ 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, (Windo… | |
++ } else if (xw.XdndSourceVersion >= 2) { | |
++ reply.xclient.message_type = xw.XdndFinished; | |
++ | |
++ XSendEvent((Display*) xw.dpy, xw.XdndSourceWin, | |
++ False, NoEventMask, &reply); | |
++ XFlush((Display*) xw.dpy); | |
+ } | |
+ } | |
+ |