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); } }