From 33d355f43e03ad5f3c020986c325e22fae02ead9 Mon Sep 17 00:00:00 2001
From: David Maciejak <david.maciejak@gmail.com>
Date: Wed, 13 Aug 2014 20:32:20 +0800
Subject: [PATCH] util/wmiv: Add drag-and-drop support

This patch is adding drag-and-drop support to wmiv based on Xdnd protocol
handling library.
---
 util/Makefile.am |   4 +-
 util/README      |   2 +
 util/wmiv.c      |  63 +++++-
 util/xdnd.c      | 679 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 util/xdnd.h      | 170 ++++++++++++++
 5 files changed, 911 insertions(+), 7 deletions(-)
 create mode 100644 util/xdnd.c
 create mode 100644 util/xdnd.h

diff --git a/util/Makefile.am b/util/Makefile.am
index b5c9d04..16a1295 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -71,11 +71,13 @@ wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \
 	wmmenugen_parse_xdg.c
 
 wmiv_LDADD = \
+	$(top_builddir)/WINGs/libWINGs.la \
+	$(top_builddir)/WINGs/libWUtil.la \
 	$(top_builddir)/wrlib/libwraster.la \
 	@XLFLAGS@ @XLIBS@ \
 	@GFXLIBS@ $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) $(LIBEXIF)
 
-wmiv_SOURCES = wmiv.c wmiv.h
+wmiv_SOURCES = wmiv.c wmiv.h xdnd.c xdnd.h
 
 CLEANFILES = wmaker.inst
 
diff --git a/util/README b/util/README
index 76b779f..73f2968 100644
--- a/util/README
+++ b/util/README
@@ -22,6 +22,8 @@ wxcopy- copy input file or stdin into X cutbuffer
 
 wxpaste- copy content of X cutbuffer into stdout
 
+wmiv- an image viewer
+
 wmsetbg- set the workspace background into a image and make it persist between
 	sessions.
 
diff --git a/util/wmiv.c b/util/wmiv.c
index 45cc4e3..c20d983 100755
--- a/util/wmiv.c
+++ b/util/wmiv.c
@@ -14,8 +14,7 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *  with this program; if not, write to the Free Software Foundation.
  */
 
 #if !defined(_GNU_SOURCE)
@@ -35,6 +34,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 #include "config.h"
+#include "xdnd.h"
 
 #ifdef HAVE_EXIF
 #include <libexif/exif-data.h>
@@ -63,7 +63,7 @@ Pixmap pix;
 
 const char *APPNAME = "wmiv";
 int APPVERSION_MAJOR = 0;
-int APPVERSION_MINOR = 7;
+int APPVERSION_MINOR = 8;
 int NEXT = 0;
 int PREV = 1;
 float zoom_factor = 0;
@@ -629,6 +629,7 @@ void linked_list_free(linked_list_t *list)
 			free((char *)link->data);
 		free(link);
 	}
+	current_link = NULL;
 }
 
 /*
@@ -686,6 +687,10 @@ int main(int argc, char **argv)
 	XClassHint *class_hints;
 	XSizeHints *size_hints;
 	XWMHints *win_hints;
+	Atom delWindow;
+	Atom xdnd_aware;
+	Atom xdnd_version = XDND_VERSION;
+
 #ifdef USE_XPM
 	Pixmap icon_pixmap, icon_shape;
 #endif
@@ -792,11 +797,13 @@ int main(int argc, char **argv)
 	size_hints->width = img->width;
 	size_hints->height = img->height;
 
-	Atom delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", 0);
+	delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
 	XSetWMProtocols(dpy, win, &delWindow, 1);
 	change_title(&title_property, reading_filename);
-
+	xdnd_aware = XInternAtom (dpy, "XdndAware", False);
+	XChangeProperty(dpy, win, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *) &xdnd_version, 1);
 	win_hints = XAllocWMHints();
+
 	if (win_hints) {
 		win_hints->flags = StateHint|InputHint|WindowGroupHint;
 
@@ -828,9 +835,46 @@ int main(int argc, char **argv)
 	while (!quit) {
 		XNextEvent(dpy, &e);
 		if (e.type == ClientMessage) {
+			unsigned char *dropBuffer;
+
+			if (xdnd_get_drop(dpy, &e, &dropBuffer)) {
+				char *handled_uri_header = "file://";
+				char *filename_separator = "\n";
+				char *ptr = strtok((char *)dropBuffer, filename_separator);
+
+				linked_list_free(&list);
+				linked_list_init(&list);
+
+				Bool find_one_file = False;
+				while (ptr != NULL) {
+					if (strstr(ptr, handled_uri_header) == ptr) {
+						char *tmp_file;
+						if (ptr[strlen(ptr) - 1] == '\r')
+							ptr[strlen(ptr) - 1] = '\0';
+						tmp_file = ptr + strlen(handled_uri_header);
+
+						/* drag-and-drop file name formatas per the spec is encoded as an URI */
+						decode_uri(tmp_file);
+						if (connect_dir(tmp_file, &list))
+							find_one_file = True;
+					}
+					ptr = strtok(NULL, filename_separator);
+				}
+				free(dropBuffer);
+
+				if (find_one_file) {
+					max_index = list.count;
+					current_link = list.last;
+					change_image(NEXT);
+				} else {
+					if (img)
+						RReleaseImage(img);
+					img = draw_failed_image();
+				}
+			}
 			if (e.xclient.data.l[0] == delWindow)
 				quit = 1;
-				break;
+			continue;
 		}
 		if (e.type == FocusIn) {
 			focus = True;
@@ -850,6 +894,13 @@ int main(int argc, char **argv)
 			XConfigureEvent xce = e.xconfigure;
 			if (xce.width != img->width || xce.height != img->height) {
 				RImage *old_img = img;
+
+				/* there is no file loaded and the window is resized */
+				if (!current_link) {
+					XResizeWindow(dpy, win, img->width, img->height);
+					continue;
+				}
+
 				img = load_oriented_image(ctx, current_link->data, 0);
 				if (!img) {
 					/* keep the old img and window size */
diff --git a/util/xdnd.c b/util/xdnd.c
new file mode 100644
index 0000000..054fd5d
--- /dev/null
+++ b/util/xdnd.c
@@ -0,0 +1,679 @@
+/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol
+   Copyright (C) 1996-2000 Paul Sheer
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation.
+*/
+
+
+/*
+   Modified 2014-08-13 - for wmiv usage
+   Released 1998-08-07
+*/
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include "xdnd.h"
+
+static void xdnd_send_status(DndClass *dnd, Window window, Window from, int will_accept, int want_position,
+						int x, int y, int w, int h, Atom action);
+static void xdnd_send_finished(DndClass *nd, Window window, Window from, int error);
+static int xdnd_convert_selection(DndClass *dnd, Window window, Window requester, Atom type);
+static int xdnd_get_selection(DndClass *dnd, Window from, Atom property, Window insert);
+
+/* #define DND_DEBUG */
+
+#define xdnd_xfree(x) { if (x) { free(x); x = 0; } }
+
+#ifdef DND_DEBUG
+
+#include <sys/time.h>
+#include <unistd.h>
+
+char *xdnd_debug_milliseconds(void)
+{
+	struct timeval tv;
+	static char r[22];
+	gettimeofday(&tv, 0);
+	sprintf(r, "%.2ld.%.3ld", tv.tv_sec % 100L, tv.tv_usec / 1000L);
+	return r;
+}
+
+#define dnd_debug1(a)       printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds())
+#define dnd_debug2(a, b)     printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b)
+#define dnd_debug3(a, b, c)   printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b, c)
+#define dnd_debug4(a, b, c, d) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b, c, d)
+#else
+#define dnd_debug1(a)
+#define dnd_debug2(a, b)
+#define dnd_debug3(a, b, c)
+#define dnd_debug4(a, b, c, d)
+#endif
+
+#define dnd_warning(a) fprintf(stderr, a)
+
+#define dnd_version_at_least(a, b) ((a) >= (b))
+
+void xdnd_reset(DndClass *dnd)
+{
+	dnd->stage = XDND_DROP_STAGE_IDLE;
+	dnd->dragging_version = 0;
+	dnd->internal_drag = 0;
+	dnd->want_position = 0;
+	dnd->ready_to_drop = 0;
+	dnd->will_accept = 0;
+	dnd->rectangle.x = dnd->rectangle.y = 0;
+	dnd->rectangle.width = dnd->rectangle.height = 0;
+	dnd->dropper_window = 0;
+	dnd->dropper_toplevel = 0;
+	dnd->dragger_window = 0;
+	dnd->dragger_typelist = 0;
+	dnd->desired_type = 0;
+	dnd->time = 0;
+}
+
+/*
+	decode_uri: transform the given URI to UTF-8 string
+*/
+void decode_uri(char *uri)
+{
+	char *last = uri + strlen(uri);
+	while (uri < last-2) {
+		if (*uri == '%') {
+			int h;
+			if (sscanf(uri+1, "%2X", &h) != 1)
+				break;
+			*uri = h;
+			memmove(uri+1, uri+3, last - (uri+2));
+			last -= 2;
+		}
+		uri++;
+	}
+}
+
+void xdnd_init(DndClass *dnd, Display *display)
+{
+	memset(dnd, 0, sizeof(*dnd));
+
+	dnd->display = display;
+	dnd->root_window = DefaultRootWindow(display);
+	dnd->version = XDND_VERSION;
+
+	dnd->XdndAware = XInternAtom(dnd->display, "XdndAware", False);
+	dnd->XdndSelection = XInternAtom(dnd->display, "XdndSelection", False);
+	dnd->XdndEnter = XInternAtom(dnd->display, "XdndEnter", False);
+	dnd->XdndLeave = XInternAtom(dnd->display, "XdndLeave", False);
+	dnd->XdndPosition = XInternAtom(dnd->display, "XdndPosition", False);
+	dnd->XdndDrop = XInternAtom(dnd->display, "XdndDrop", False);
+	dnd->XdndFinished = XInternAtom(dnd->display, "XdndFinished", False);
+	dnd->XdndStatus = XInternAtom(dnd->display, "XdndStatus", False);
+	dnd->XdndActionCopy = XInternAtom(dnd->display, "XdndActionCopy", False);
+	dnd->XdndActionMove = XInternAtom(dnd->display, "XdndActionMove", False);
+	dnd->XdndActionLink = XInternAtom(dnd->display, "XdndActionLink", False);
+	dnd->XdndActionAsk = XInternAtom(dnd->display, "XdndActionAsk", False);
+	dnd->XdndActionPrivate = XInternAtom(dnd->display, "XdndActionPrivate", False);
+	dnd->XdndTypeList = XInternAtom(dnd->display, "XdndTypeList", False);
+	dnd->XdndActionList = XInternAtom(dnd->display, "XdndActionList", False);
+	dnd->XdndActionDescription = XInternAtom(dnd->display, "XdndActionDescription", False);
+
+	dnd->Xdnd_NON_PROTOCOL_ATOM = XInternAtom(dnd->display, "JXSelectionWindowProperty", False);
+
+	xdnd_reset(dnd);
+}
+
+/* typelist is a null terminated array */
+static int array_length(Atom *a)
+{
+	int n;
+	for (n = 0; a[n]; n++);
+	return n;
+}
+
+void xdnd_set_type_list(DndClass *dnd, Window window, Atom *typelist)
+{
+	int n;
+	n = array_length(typelist);
+	XChangeProperty(dnd->display, window, dnd->XdndTypeList, XA_ATOM, 32,
+					 PropModeReplace, (unsigned char *) typelist, n);
+}
+
+/* result must be free'd */
+void xdnd_get_type_list(DndClass *dnd, Window window, Atom **typelist)
+{
+	Atom type, *a;
+	int format, i;
+	unsigned long count, remaining;
+	unsigned char *data = NULL;
+
+	*typelist = 0;
+
+	XGetWindowProperty(dnd->display, window, dnd->XdndTypeList,
+						0, 0x8000000L, False, XA_ATOM,
+						&type, &format, &count, &remaining, &data);
+
+	if (type != XA_ATOM || format != 32 || count == 0 || !data) {
+		if (data)
+			XFree(data);
+		dnd_debug2("XGetWindowPropertyfailed in xdnd_get_type_list- dnd->XdndTypeList = %ld", dnd->XdndTypeList);
+		return;
+	}
+	*typelist = malloc((count + 1) * sizeof(Atom));
+	a = (Atom *) data;
+	for (i = 0; i < count; i++)
+		(*typelist)[i] = a[i];
+	(*typelist)[count] = 0;
+
+	XFree(data);
+}
+
+void xdnd_get_three_types(DndClass *dnd, XEvent *xevent, Atom **typelist)
+{
+	int i;
+	(void) dnd;
+
+	*typelist = malloc((XDND_THREE + 1) * sizeof(Atom));
+	for (i = 0; i < XDND_THREE; i++)
+		(*typelist)[i] = XDND_ENTER_TYPE(xevent, i);
+	/* although (*typelist)[1] or (*typelist)[2] may also be set to nill */
+	(*typelist)[XDND_THREE] = 0;
+}
+
+static void xdnd_send_event(DndClass *dnd, Window window, XEvent *xevent)
+{
+	dnd_debug4("xdnd_send_event(), window = %ld, l[0] = %ld, l[4] = %ld",
+	window, xevent->xclient.data.l[0], xevent->xclient.data.l[4]);
+	dnd_debug2("xdnd_send_event(), from widget widget %s", (char *) "<WIDGET>");
+	XSendEvent(dnd->display, window, 0, 0, xevent);
+}
+
+static void xdnd_send_status(DndClass *dnd, Window window, Window from, int will_accept,
+			  int want_position, int x, int y, int w, int h, Atom action)
+{
+	XEvent xevent;
+
+	memset(&xevent, 0, sizeof(xevent));
+
+	xevent.xany.type = ClientMessage;
+	xevent.xany.display = dnd->display;
+	xevent.xclient.window = window;
+	xevent.xclient.message_type = dnd->XdndStatus;
+	xevent.xclient.format = 32;
+
+	XDND_STATUS_TARGET_WIN(&xevent) = from;
+	XDND_STATUS_WILL_ACCEPT_SET(&xevent, will_accept);
+	if (will_accept)
+		XDND_STATUS_WANT_POSITION_SET(&xevent, want_position);
+	if (want_position)
+		XDND_STATUS_RECT_SET(&xevent, x, y, w, h);
+	if (dnd_version_at_least(dnd->dragging_version, 2))
+		if (will_accept)
+			XDND_STATUS_ACTION(&xevent) = action;
+
+	xdnd_send_event(dnd, window, &xevent);
+}
+
+
+/* error is not actually used, i think future versions of the protocol should return an error status
+   to the calling window with the XdndFinished client message */
+static void xdnd_send_finished(DndClass *dnd, Window window, Window from, int error)
+{
+	XEvent xevent;
+	(void) error;
+
+	memset(&xevent, 0, sizeof(xevent));
+
+	xevent.xany.type = ClientMessage;
+	xevent.xany.display = dnd->display;
+	xevent.xclient.window = window;
+	xevent.xclient.message_type = dnd->XdndFinished;
+	xevent.xclient.format = 32;
+
+	XDND_FINISHED_TARGET_WIN(&xevent) = from;
+
+	xdnd_send_event(dnd, window, &xevent);
+}
+
+/* returns non-zero on error - i.e. no selection owner set. Type is of course the mime type */
+static int xdnd_convert_selection(DndClass *dnd, Window window, Window requester, Atom type)
+{
+	window = XGetSelectionOwner(dnd->display, dnd->XdndSelection);
+	if (!window) {
+		dnd_debug1("xdnd_convert_selection(): XGetSelectionOwner failed");
+		return 1;
+	}
+	XConvertSelection(dnd->display, dnd->XdndSelection, type,
+					dnd->Xdnd_NON_PROTOCOL_ATOM, requester, CurrentTime);
+	return 0;
+}
+
+static int paste_prop_internal(DndClass *dnd, Window from, Window insert, unsigned long prop, int delete_prop)
+{
+	long nread = 0;
+	unsigned long nitems;
+	unsigned long bytes_after;
+	int error = 0;
+	do {
+		Atom actual_type;
+		int actual_fmt;
+		unsigned char *s = 0;
+		if (XGetWindowProperty(dnd->display, insert, prop,
+								nread / 4, 65536, delete_prop,
+								AnyPropertyType, &actual_type, &actual_fmt,
+								&nitems, &bytes_after, &s) != Success) {
+			XFree(s);
+			return 1;
+		}
+		nread += nitems;
+		if (dnd->widget_insert_drop && !error)
+			error = (*dnd->widget_insert_drop) (dnd, s, nitems, bytes_after, insert, from, actual_fmt);
+		XFree(s);
+	} while (bytes_after);
+	if (!nread)
+		return 1;
+	return 0;
+}
+
+/*
+ * Respond to a notification that a primary selection has been sent (supports INCR)
+ */
+static int xdnd_get_selection(DndClass *dnd, Window from, Atom prop, Window insert)
+{
+	struct timeval tv, tv_start;
+	unsigned long bytes_after;
+	Atom actual_type;
+	int actual_fmt;
+	unsigned long nitems;
+	unsigned char *s = 0;
+	if (prop == None)
+		return 1;
+	if (XGetWindowProperty
+		(dnd->display, insert, prop, 0, 8, False, AnyPropertyType, &actual_type, &actual_fmt,
+		 &nitems, &bytes_after, &s) != Success) {
+		XFree(s);
+		return 1;
+	}
+	XFree(s);
+	if (actual_type != XInternAtom(dnd->display, "INCR", False))
+		return paste_prop_internal(dnd, from, insert, prop, True);
+	XDeleteProperty(dnd->display, insert, prop);
+	gettimeofday(&tv_start, 0);
+	for (;;) {
+		long t;
+		fd_set r;
+		XEvent xe;
+		if (XCheckMaskEvent(dnd->display, PropertyChangeMask, &xe)) {
+			if (xe.type == PropertyNotify && xe.xproperty.state == PropertyNewValue) {
+				/* time between arrivals of data */
+				gettimeofday(&tv_start, 0);
+				if (paste_prop_internal(dnd, from, insert, prop, True))
+					break;
+			}
+		} else {
+			tv.tv_sec = 0;
+			tv.tv_usec = 10000;
+			FD_ZERO(&r);
+			FD_SET(ConnectionNumber(dnd->display), &r);
+			select(ConnectionNumber(dnd->display) + 1, &r, 0, 0, &tv);
+			if (FD_ISSET(ConnectionNumber(dnd->display), &r))
+				continue;
+		}
+		gettimeofday(&tv, 0);
+		t = (tv.tv_sec - tv_start.tv_sec) * 1000000L + (tv.tv_usec - tv_start.tv_usec);
+		/* no data for five seconds, so quit */
+		if (t > 5000000L)
+			return 1;
+	}
+	return 0;
+}
+
+/* returns non-zero if event is handled */
+int xdnd_handle_drop_events(DndClass *dnd, XEvent *xevent)
+{
+	int result = 0;
+	if (xevent->type == SelectionNotify) {
+		dnd_debug1("got SelectionNotify");
+		if (xevent->xselection.property == dnd->Xdnd_NON_PROTOCOL_ATOM && dnd->stage == XDND_DROP_STAGE_CONVERTING) {
+			int error;
+			dnd_debug1("  property is Xdnd_NON_PROTOCOL_ATOM - getting selection");
+			error = xdnd_get_selection(dnd, dnd->dragger_window, xevent->xselection.property, xevent->xany.window);
+			/* error is not actually used, i think future versions of the protocol maybe should return
+			   an error status to the calling window with the XdndFinished client message */
+			if (dnd_version_at_least(dnd->dragging_version, 2)) {
+#if XDND_VERSION >= 3
+				xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_toplevel, error);
+#else
+				xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_window, error);
+#endif
+				dnd_debug1("	sending finished");
+			}
+			xdnd_xfree(dnd->dragger_typelist);
+			xdnd_reset(dnd);
+			dnd->stage = XDND_DROP_STAGE_IDLE;
+			result = 1;
+		} else {
+			dnd_debug1("  property is not Xdnd_NON_PROTOCOL_ATOM - ignoring");
+		}
+	} else if (xevent->type == ClientMessage) {
+		dnd_debug2("got ClientMessage to xevent->xany.window = %ld", xevent->xany.window);
+		if (xevent->xclient.message_type == dnd->XdndEnter) {
+			dnd_debug2("  message_type is XdndEnter, version = %ld", XDND_ENTER_VERSION(xevent));
+#if XDND_VERSION >= 3
+			if (XDND_ENTER_VERSION(xevent) < 3)
+				return 0;
+#endif
+			xdnd_reset(dnd);
+			dnd->dragger_window = XDND_ENTER_SOURCE_WIN(xevent);
+#if XDND_VERSION >= 3
+			dnd->dropper_toplevel = xevent->xany.window;
+			dnd->dropper_window = 0;	 /* enter goes to the top level window only,
+									so we don't really know what the
+									sub window is yet */
+#else
+			dnd->dropper_window = xevent->xany.window;
+#endif
+			xdnd_xfree(dnd->dragger_typelist);
+			if (XDND_ENTER_THREE_TYPES(xevent)) {
+				dnd_debug1("	three types only");
+				xdnd_get_three_types(dnd, xevent, &dnd->dragger_typelist);
+			} else {
+				dnd_debug1("	more than three types - getting list");
+				xdnd_get_type_list(dnd, dnd->dragger_window, &dnd->dragger_typelist);
+			}
+			if (dnd->dragger_typelist)
+				dnd->stage = XDND_DROP_STAGE_ENTERED;
+			else {
+				dnd_debug1("typelist returned as zero!");
+			}
+
+			dnd->dragging_version = XDND_ENTER_VERSION(xevent);
+			result = 1;
+		} else if (xevent->xclient.message_type == dnd->XdndLeave) {
+#if XDND_VERSION >= 3
+			if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window)
+				xevent->xany.window = dnd->dropper_window;
+#endif
+			dnd_debug1("  message_type is XdndLeave");
+			if (dnd->dragger_window == XDND_LEAVE_SOURCE_WIN(xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
+				dnd_debug1("	leaving");
+				if (dnd->widget_apply_leave)
+					(*dnd->widget_apply_leave) (dnd, xevent->xany.window);
+				dnd->stage = XDND_DROP_STAGE_IDLE;
+				xdnd_xfree(dnd->dragger_typelist);
+				result = 1;
+				dnd->dropper_toplevel = dnd->dropper_window = 0;
+			} else {
+				dnd_debug1("	wrong stage or from wrong window");
+			}
+		} else if (xevent->xclient.message_type == dnd->XdndPosition) {
+			dnd_debug2("  message_type is XdndPosition to %ld", xevent->xany.window);
+			if (dnd->dragger_window == XDND_POSITION_SOURCE_WIN(xevent)
+				&& dnd->stage == XDND_DROP_STAGE_ENTERED) {
+				int want_position;
+				Atom action;
+				XRectangle rectangle;
+				Window last_window;
+				last_window = dnd->dropper_window;
+#if XDND_VERSION >= 3
+				/* version 3 gives us the top-level window only.
+				   WE have to find the child that the pointer is over: */
+				if (1 || xevent->xany.window != dnd->dropper_toplevel || !dnd->dropper_window) {
+					Window parent, child, new_child = 0;
+					dnd->dropper_toplevel = xevent->xany.window;
+					parent = dnd->root_window;
+					child = dnd->dropper_toplevel;
+					for (;;) {
+						int xd, yd;
+						new_child = 0;
+						if (!XTranslateCoordinates(dnd->display, parent, child,
+									XDND_POSITION_ROOT_X(xevent), XDND_POSITION_ROOT_Y(xevent),
+									&xd, &yd, &new_child))
+							break;
+						if (!new_child)
+							break;
+						child = new_child;
+					}
+					dnd->dropper_window = xevent->xany.window = child;
+					dnd_debug2("   child window translates to %ld", dnd->dropper_window);
+				} else if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window) {
+					xevent->xany.window = dnd->dropper_window;
+					dnd_debug2("   child window previously found: %ld", dnd->dropper_window);
+				}
+#endif
+				action = dnd->XdndActionCopy;
+				dnd->supported_action = dnd->XdndActionCopy;
+				dnd->x = XDND_POSITION_ROOT_X(xevent);
+				dnd->y = XDND_POSITION_ROOT_Y(xevent);
+				dnd->time = CurrentTime;
+				if (dnd_version_at_least(dnd->dragging_version, 1))
+					dnd->time = XDND_POSITION_TIME(xevent);
+				if (dnd_version_at_least(dnd->dragging_version, 1))
+					action = XDND_POSITION_ACTION(xevent);
+#if XDND_VERSION >= 3
+				if (last_window && last_window != xevent->xany.window)
+					if (dnd->widget_apply_leave)
+						(*dnd->widget_apply_leave) (dnd, last_window);
+#endif
+				dnd->will_accept = (*dnd->widget_apply_position) (dnd, xevent->xany.window, dnd->dragger_window,
+									action, dnd->x, dnd->y, dnd->time, dnd->dragger_typelist,
+									&want_position, &dnd->supported_action, &dnd->desired_type, &rectangle);
+				dnd_debug2("	will accept = %d", dnd->will_accept);
+#if XDND_VERSION >= 3
+				dnd_debug2("	sending status of %ld", dnd->dropper_toplevel);
+				xdnd_send_status(dnd, dnd->dragger_window, dnd->dropper_toplevel, dnd->will_accept,
+								want_position, rectangle.x, rectangle.y,
+								rectangle.width, rectangle.height, dnd->supported_action);
+#else
+				dnd_debug2("	sending status of %ld", xevent->xany.window);
+				xdnd_send_status(dnd, dnd->dragger_window, xevent->xany.window, dnd->will_accept,
+								want_position, rectangle.x, rectangle.y, rectangle.width,
+								rectangle.height, dnd->supported_action);
+#endif
+				result = 1;
+			} else {
+				dnd_debug1("	wrong stage or from wrong window");
+			}
+		} else if (xevent->xclient.message_type == dnd->XdndDrop) {
+#if XDND_VERSION >= 3
+			if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window)
+				xevent->xany.window = dnd->dropper_window;
+#endif
+			dnd_debug1("  message_type is XdndDrop");
+			if (dnd->dragger_window == XDND_DROP_SOURCE_WIN(xevent)
+				&& dnd->stage == XDND_DROP_STAGE_ENTERED) {
+				dnd->time = CurrentTime;
+				if (dnd_version_at_least(dnd->dragging_version, 1))
+					dnd->time = XDND_DROP_TIME(xevent);
+				if (dnd->will_accept) {
+					dnd_debug1("	will_accept is true - converting selectiong");
+					dnd_debug2("	  my window is %ld", dnd->dropper_window);
+					dnd_debug2("		source window is %ld", dnd->dragger_window);
+					xdnd_convert_selection(dnd, dnd->dragger_window, dnd->dropper_window, dnd->desired_type);
+					dnd->stage = XDND_DROP_STAGE_CONVERTING;
+				} else {
+					dnd_debug1("	will_accept is false - sending finished");
+					if (dnd_version_at_least(dnd->dragging_version, 2)) {
+#if XDND_VERSION >= 3
+						xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_toplevel, 1);
+#else
+						xdnd_send_finished(dnd, dnd->dragger_window, xevent->xany.window, 1);
+#endif
+					}
+					xdnd_xfree(dnd->dragger_typelist);
+					xdnd_reset(dnd);
+					dnd->stage = XDND_DROP_STAGE_IDLE;
+				}
+				result = 1;
+			} else {
+				dnd_debug1("	wrong stage or from wrong window");
+			}
+		}
+	}
+	return result;
+}
+
+struct xdnd_get_drop_info {
+	unsigned char *drop_data;
+	int drop_data_length;
+	int x, y;
+	Atom return_type;
+	Atom return_action;
+	Atom *typelist;
+	Atom *actionlist;
+};
+
+static int widget_insert_drop(DndClass *dnd, unsigned char *data, int length,
+						int remaining, Window into, Window from, Atom type)
+{
+	struct xdnd_get_drop_info *i;
+	(void) remaining;
+	(void) into;
+	(void) from;
+	(void) type;
+
+	i = (struct xdnd_get_drop_info *) dnd->user_hook1;
+	if (!i->drop_data) {
+		i->drop_data = malloc(length);
+		if (!i->drop_data)
+			return 1;
+		memcpy(i->drop_data, data, length);
+		i->drop_data_length = length;
+	} else {
+		unsigned char *t;
+		t = malloc(i->drop_data_length + length);
+		if (!t) {
+			free(i->drop_data);
+			i->drop_data = 0;
+			return 1;
+		}
+		memcpy(t, i->drop_data, i->drop_data_length);
+		memcpy(t + i->drop_data_length, data, length);
+		free(i->drop_data);
+		i->drop_data = t;
+		i->drop_data_length += length;
+	}
+	return 0;
+}
+
+static int widget_apply_position(DndClass *dnd, Window widgets_window, Window from,
+						Atom action, int x, int y, Time t, Atom *typelist,
+						int *want_position, Atom *supported_action_return,
+						Atom *desired_type, XRectangle *rectangle)
+{
+	int i, j;
+	struct xdnd_get_drop_info *info;
+	Atom *dropper_typelist, supported_type = 0;
+	Atom *supported_actions, supported_action = 0;
+	(void) widgets_window;
+	(void) from;
+	(void) t;
+
+	info = (struct xdnd_get_drop_info *) dnd->user_hook1;
+	dropper_typelist = info->typelist;
+	supported_actions = info->actionlist;
+
+	if (dropper_typelist) {
+		/* find a correlation: */
+		for (j = 0; dropper_typelist[j]; j++) {
+			for (i = 0; typelist[i]; i++) {
+				if (typelist[i] == dropper_typelist[j]) {
+					supported_type = typelist[i];
+					break;
+				}
+			}
+			if (supported_type)
+				break;
+		}
+	} else {
+		/* user did not specify, so return first type */
+		supported_type = typelist[0];
+	}
+	/* not supported, so return false */
+	if (!supported_type)
+		return 0;
+
+	if (supported_actions) {
+		for (j = 0; supported_actions[j]; j++) {
+			if (action == supported_actions[j]) {
+				supported_action = action;
+				break;
+			}
+		}
+	} else {
+		/* user did not specify */
+		if (action == dnd->XdndActionCopy)
+			supported_action = action;
+	}
+	if (!supported_action)
+		return 0;
+
+	*want_position = 1;
+	rectangle->x = rectangle->y = 0;
+	rectangle->width = rectangle->height = 0;
+
+	info->return_action = *supported_action_return = supported_action;
+	info->return_type = *desired_type = supported_type;
+	info->x = x;
+	info->y = y;
+
+	return 1;
+}
+
+Atom xdnd_get_drop(Display *display, XEvent *xevent, unsigned char **data)
+{
+	Atom action = 0;
+	static int initialised = 0;
+	static DndClass dnd;
+	static Atom typelist[2];
+
+	if (!initialised) {
+		const char *m_dndMimeTypes[] = { "text/uri-list" };
+		xdnd_init(&dnd, display);
+		XInternAtoms(display, (char **)m_dndMimeTypes, 1, 0, typelist);
+		typelist[1] = 0;
+		initialised = 1;
+	}
+
+	if (xevent->type != ClientMessage || xevent->xclient.message_type != dnd.XdndEnter) {
+		return 0;
+	} else {
+		struct xdnd_get_drop_info i;
+
+		/* setup user structure */
+		memset(&i, 0, sizeof(i));
+		i.typelist = typelist;
+		dnd.user_hook1 = &i;
+
+		/* setup methods */
+		dnd.widget_insert_drop = widget_insert_drop;
+		dnd.widget_apply_position = widget_apply_position;
+
+		/* main loop */
+		for (;;) {
+			xdnd_handle_drop_events(&dnd, xevent);
+			if (dnd.stage == XDND_DROP_STAGE_IDLE)
+				break;
+			XNextEvent(dnd.display, xevent);
+		}
+		/* return results */
+		if (i.drop_data) {
+			*data = i.drop_data;
+			action = i.return_action;
+		}
+	}
+	return action;
+}
diff --git a/util/xdnd.h b/util/xdnd.h
new file mode 100644
index 0000000..8d95c3c
--- /dev/null
+++ b/util/xdnd.h
@@ -0,0 +1,170 @@
+/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol
+   Copyright (C) 1996-2000 Paul Sheer
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation.
+ */
+
+#ifndef _X_DND_H
+#define _X_DND_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* you can set this to either 2 (which support 0 and 1 as well) or 3 */
+#define XDND_VERSION 3
+
+
+/* XdndEnter */
+#define XDND_THREE 3
+#define XDND_ENTER_SOURCE_WIN(e)	((e)->xclient.data.l[0])
+#define XDND_ENTER_THREE_TYPES(e)	(((e)->xclient.data.l[1] & 0x1UL) == 0)
+#define XDND_ENTER_THREE_TYPES_SET(e, b)	(e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL)
+#define XDND_ENTER_VERSION(e)		((e)->xclient.data.l[1] >> 24)
+#define XDND_ENTER_VERSION_SET(e, v)	(e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
+#define XDND_ENTER_TYPE(e, i)		((e)->xclient.data.l[2 + i])	/* i => (0, 1, 2) */
+
+/* XdndPosition */
+#define XDND_POSITION_SOURCE_WIN(e)	((e)->xclient.data.l[0])
+#define XDND_POSITION_ROOT_X(e)		((e)->xclient.data.l[2] >> 16)
+#define XDND_POSITION_ROOT_Y(e)		((e)->xclient.data.l[2] & 0xFFFFUL)
+#define XDND_POSITION_ROOT_SET(e, x, y)	(e)->xclient.data.l[2]  = ((x) << 16) | ((y) & 0xFFFFUL)
+#define XDND_POSITION_TIME(e)		((e)->xclient.data.l[3])
+#define XDND_POSITION_ACTION(e)		((e)->xclient.data.l[4])
+
+/* XdndStatus */
+#define XDND_STATUS_TARGET_WIN(e)	((e)->xclient.data.l[0])
+#define XDND_STATUS_WILL_ACCEPT(e)	((e)->xclient.data.l[1] & 0x1L)
+#define XDND_STATUS_WILL_ACCEPT_SET(e, b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL)
+#define XDND_STATUS_WANT_POSITION(e)	((e)->xclient.data.l[1] & 0x2UL)
+#define XDND_STATUS_WANT_POSITION_SET(e, b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x2UL) | (((b) == 0) ? 0 : 0x2UL)
+#define XDND_STATUS_RECT_X(e)		((e)->xclient.data.l[2] >> 16)
+#define XDND_STATUS_RECT_Y(e)		((e)->xclient.data.l[2] & 0xFFFFL)
+#define XDND_STATUS_RECT_WIDTH(e)	((e)->xclient.data.l[3] >> 16)
+#define XDND_STATUS_RECT_HEIGHT(e)	((e)->xclient.data.l[3] & 0xFFFFL)
+#define XDND_STATUS_RECT_SET(e, x, y, w, h)	{(e)->xclient.data.l[2] = ((x) << 16) | ((y) & 0xFFFFUL); (e)->xclient.data.l[3] = ((w) << 16) | ((h) & 0xFFFFUL); }
+#define XDND_STATUS_ACTION(e)		((e)->xclient.data.l[4])
+
+/* XdndLeave */
+#define XDND_LEAVE_SOURCE_WIN(e)	((e)->xclient.data.l[0])
+
+/* XdndDrop */
+#define XDND_DROP_SOURCE_WIN(e)		((e)->xclient.data.l[0])
+#define XDND_DROP_TIME(e)		((e)->xclient.data.l[2])
+
+/* XdndFinished */
+#define XDND_FINISHED_TARGET_WIN(e)	((e)->xclient.data.l[0])
+
+typedef struct _DndClass DndClass;
+
+struct _DndClass {
+/* insert chars sequentionally into the target widget, type will be the same as `desired_type'
+   returned from widget_apply_position. This may be called several times in succession
+   with sequention blocks of data. Must return non-zero on failure */
+	int (*widget_insert_drop)(DndClass *dnd, unsigned char *data, int length,
+						int remaining, Window into, Window from, Atom type);
+
+/* must update the widgets border to its default appearance */
+	void (*widget_apply_leave)(DndClass *dnd, Window widgets_window);
+
+/* must update the widgets border to give the appearance of being able to recieve a drop,
+   plus return all data to pointers. As per the protocol, if the widget cannot
+   perform the action specified by `action' then it should return either XdndActionPrivate
+   or XdndActionCopy into supported_action (leaving 0 supported_action unchanged is equivalent
+   to XdndActionCopy). Returns 1 if ready to ok drop */
+	int (*widget_apply_position)(DndClass *dnd, Window widgets_window, Window from,
+					Atom action, int x, int y, Time t, Atom *typelist, int *want_position,
+					Atom *supported_action, Atom *desired_type, XRectangle *rectangle);
+
+	void *pad1[8];
+
+	Display *display;
+
+	Atom XdndAware;
+	Atom XdndSelection;
+	Atom XdndEnter;
+	Atom XdndLeave;
+	Atom XdndPosition;
+	Atom XdndDrop;
+	Atom XdndFinished;
+	Atom XdndStatus;
+	Atom XdndActionCopy;
+	Atom XdndActionMove;
+	Atom XdndActionLink;
+	Atom XdndActionAsk;
+	Atom XdndActionPrivate;
+	Atom XdndTypeList;
+	Atom XdndActionList;
+	Atom XdndActionDescription;
+
+	Atom Xdnd_NON_PROTOCOL_ATOM;
+	Atom version;
+
+	Atom pad2[16];
+
+	Window root_window;
+
+#define XDND_DROP_STAGE_IDLE		0
+#define XDND_DRAG_STAGE_DRAGGING	1
+#define XDND_DRAG_STAGE_ENTERED		2
+#define XDND_DROP_STAGE_CONVERTING	3
+#define XDND_DROP_STAGE_ENTERED		4
+	int stage;
+	int dragging_version;
+	int internal_drag;
+	int want_position;
+	int ready_to_drop;
+	int will_accept;
+	XRectangle rectangle;
+	Window dropper_window, dragger_window;
+	Atom *dragger_typelist;
+	Atom desired_type;
+	Atom supported_action;
+	Time time;
+/* drop position from last XdndPosition */
+	int x, y;
+	int pad3[16];
+
+/* move euclidian pixels before considering this to be an actual drag */
+	float drag_threshold;
+
+/* block for only this many seconds on not receiving a XdndFinished from target, default : 10 */
+	int time_out;
+
+#define XDND_OPTION_NO_HYSTERESIS (1<<0)
+	int options;
+
+/* user hooks */
+	void *user_hook1;
+	void *user_hook2;
+	void *user_hook3;
+	Window dropper_toplevel;
+	void *pad4[15];
+};
+
+void decode_uri(char *uri);
+void xdnd_init(DndClass *dnd, Display *display);
+/* Returns 1 if event is handled, This must be placed in the widget
+libraries main event loop and be called if the event type is
+ClientMessage or SelectionNotify */
+int xdnd_handle_drop_events(DndClass *dnd, XEvent *xevent);
+Atom xdnd_get_drop(Display *display, XEvent *xevent, unsigned char **data);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_X_DND_H */
-- 
1.8.3.2

