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:
Ast.suckless.org/patches/drag-n-drop/index.md | 24++++++++++++++++++++++++
Ast.suckless.org/patches/drag-n-drop/st-drag-n-drop-0.9.2.diff | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 367 insertions(+), 0 deletions(-)

diff --git a/st.suckless.org/patches/drag-n-drop/index.md 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 - <tjk@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 @@ -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 = '\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.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); + } + } +