/* 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, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */


/*
   Released 1998-08-07
   Changes:

   2000-08-08: INCR protocol implemented.

*/

/*
    DONE:
     - INCR protocol now implemented

    TODO:
     - action_choose_dialog not yet supported (never called)
     - widget_delete_selection not yet supported and DELETE requests are ignored
     - not yet tested with applications that only supported XDND 0 or 1
*/

#include <X11/Xlib.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef HAVE_SYS_SELECT_H
#  include <sys/select.h>
#endif

#include "xdnd.h"

static void xdnd_send_enter (DndClass * dnd, Window window, Window from, Atom * typelist);
static void xdnd_send_position (DndClass * dnd, Window window, Window from, Atom action, int x, int y,
                                unsigned long etime);
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_leave (DndClass * dnd, Window window, Window from);
static void xdnd_send_drop (DndClass * dnd, Window window, Window from, unsigned long etime);
static void xdnd_send_finished (DndClass * dnd, Window window, Window from, int error);
static int xdnd_convert_selection (DndClass * dnd, Window window, Window requester, Atom type);
static void xdnd_selection_send (DndClass * dnd, XSelectionRequestEvent * request, unsigned char *data,
                                 int length);
static int xdnd_get_selection (DndClass * dnd, Window from, Atom property, Window insert);


/* just to remind us : */

#if 0
typedef struct {
    int type;
    unsigned long serial;
    Bool send_event;
    Display *display;
    Window window;
    Atom message_type;
    int format;
    union {
        char b[20];
        short s[10];
        long l[5];
    } data;
} XClientMessageEvent;
XClientMessageEvent xclient;
#endif

/* #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)       do {} while (0)
#define dnd_debug2(a,b)     do {} while (0)
#define dnd_debug3(a,b,c)   do {} while (0)
#define dnd_debug4(a,b,c,d) do {} while (0)
#endif

#define dnd_warning(a) fprintf (stderr, a)

#define dnd_version_at_least(a,b) ((a) >= (b))

static unsigned char dnd_copy_cursor_bits[] =
{
  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
  0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0xe8, 0x0f,
  0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01,
  0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
  0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
  0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
  0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
  0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00};

static unsigned char dnd_copy_mask_bits[] =
{
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
  0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
  0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
  0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
  0x00, 0x80, 0x01, 0x00};

static unsigned char dnd_move_cursor_bits[] =
{
  0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
  0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
  0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x04, 0x08, 0x02, 0x0c, 0x08,
  0x02, 0x1c, 0x08, 0x02, 0x3c, 0x08, 0x02, 0x7c, 0x08, 0x02, 0xfc, 0x08,
  0x02, 0xfc, 0x09, 0x02, 0xfc, 0x0b, 0x02, 0x7c, 0x08, 0xfe, 0x6d, 0x0f,
  0x00, 0xc4, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01,
  0x00, 0x00, 0x00};

static unsigned char dnd_move_mask_bits[] =
{
  0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0x07, 0x00, 0x1c,
  0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c,
  0x07, 0x00, 0x1c, 0x07, 0x06, 0x1c, 0x07, 0x0e, 0x1c, 0x07, 0x1e, 0x1c,
  0x07, 0x3e, 0x1c, 0x07, 0x7e, 0x1c, 0x07, 0xfe, 0x1c, 0x07, 0xfe, 0x1d,
  0x07, 0xfe, 0x1f, 0x07, 0xfe, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1e,
  0xff, 0xef, 0x1f, 0x00, 0xe6, 0x01, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03,
  0x00, 0x80, 0x01};

static unsigned char dnd_link_cursor_bits[] =
{
  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
  0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0xe8, 0x0f,
  0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x08, 0x01,
  0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
  0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
  0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
  0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
  0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00};

static unsigned char dnd_link_mask_bits[] =
{
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
  0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
  0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
  0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
  0x00, 0x80, 0x01, 0x00};

static unsigned char dnd_ask_cursor_bits[] =
{
  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x88, 0x03,
  0x02, 0x00, 0x48, 0x04, 0x02, 0x00, 0x08, 0x04, 0x02, 0x00, 0x08, 0x02,
  0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x00,
  0x02, 0x00, 0x08, 0x01, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
  0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
  0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
  0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
  0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00};

static unsigned char dnd_ask_mask_bits[] =
{
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
  0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
  0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
  0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
  0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
  0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
  0x00, 0x80, 0x01, 0x00};

static DndCursor dnd_cursors[] =
{
    {29, 25, 10, 10, dnd_copy_cursor_bits, dnd_copy_mask_bits, "XdndActionCopy", 0, 0, 0, 0},
    {21, 25, 10, 10, dnd_move_cursor_bits, dnd_move_mask_bits, "XdndActionMove", 0, 0, 0, 0},
    {29, 25, 10, 10, dnd_link_cursor_bits, dnd_link_mask_bits, "XdndActionLink", 0, 0, 0, 0},
    {29, 25, 10, 10, dnd_ask_cursor_bits, dnd_ask_mask_bits, "XdndActionAsk", 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};

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

void xdnd_init (DndClass * dnd, Display * display)
{
    DndCursor *cursor;
    XColor black, white;
    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);

    dnd->cursors = dnd_cursors;

    black.pixel = BlackPixel (dnd->display, DefaultScreen (dnd->display));
    white.pixel = WhitePixel (dnd->display, DefaultScreen (dnd->display));

    XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &black);
    XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &white);

    for (cursor = &dnd->cursors[0]; cursor->width; cursor++) {
        cursor->image_pixmap = XCreateBitmapFromData \
            (dnd->display, dnd->root_window, (char *) cursor->image_data, cursor->width, cursor->height);
        cursor->mask_pixmap = XCreateBitmapFromData \
            (dnd->display, dnd->root_window, (char *) cursor->mask_data, cursor->width, cursor->height);
        cursor->cursor = XCreatePixmapCursor (dnd->display, cursor->image_pixmap,
              cursor->mask_pixmap, &black, &white, cursor->x, cursor->y);
        XFreePixmap (dnd->display, cursor->image_pixmap);
        XFreePixmap (dnd->display, cursor->mask_pixmap);
        cursor->action = XInternAtom (dnd->display, cursor->_action, False);
    }
}

void xdnd_shut (DndClass * dnd)
{
    DndCursor *cursor;
    for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
        XFreeCursor (dnd->display, cursor->cursor);
    memset (dnd, 0, sizeof (*dnd));
    return;
}


/* 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_dnd_aware (DndClass * dnd, Window window, Atom * typelist)
{
    Window root_return, parent;
    unsigned int nchildren_return;
    Window *children_return = 0;
    int r, s;
    if(!window) return;
    if (dnd->widget_exists)
        if (!(*dnd->widget_exists) (dnd, window))
            return;
    s = XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeReplace,
                         (unsigned char *) &dnd->version, 1);
#if 1
    dnd_debug4 ("XChangeProperty() = %d, window = %ld, widget = %s", s, window, "<WIDGET>");
#endif
    if (s && typelist) {
        int n;
        n = array_length (typelist);
        if (n)
            s = XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeAppend,
                                 (unsigned char *) typelist, n);
    }
    r =
        XQueryTree (dnd->display, window, &root_return, &parent, &children_return,
                    &nchildren_return);
    if (children_return)
        XFree (children_return);
    if (r && parent != root_return)
        xdnd_set_dnd_aware (dnd, parent, typelist);
}

int xdnd_is_dnd_aware (DndClass * dnd, Window window, int *version, Atom * typelist)
{
    Atom actual;
    int format;
    unsigned long count, remaining;
    unsigned char *data = 0;
    Atom *types, *t;
    int result = 1;

    *version = 0;
    XGetWindowProperty (dnd->display, window, dnd->XdndAware,
                        0, 0x8000000L, False, XA_ATOM,
                        &actual, &format,
                        &count, &remaining, &data);

    if (actual != XA_ATOM || format != 32 || count == 0 || !data) {
        dnd_debug2 ("XGetWindowProperty failed in xdnd_is_dnd_aware - XdndAware = %ld", dnd->XdndAware);
        if (data)
            XFree (data);
        return 0;
    }
    types = (Atom *) data;
#if XDND_VERSION >= 3
    if (types[0] < 3) {
        if (data)
            XFree (data);
        return 0;
    }
#endif
    *version = dnd->version < types[0] ? dnd->version : types[0];        /* minimum */
    dnd_debug2 ("Using XDND version %d", *version);
    if (count > 1) {
        result = 0;
        for (t = typelist; *t; t++) {
            int j;
            for (j = 1; j < count; j++) {
                if (types[j] == *t) {
                    result = 1;
                    break;
                }
            }
            if (result)
                break;
        }
    }
    XFree (data);
    return result;
}

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 ("XGetWindowProperty failed 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);
    (*typelist)[XDND_THREE] = 0;        /* although (*typelist)[1] or (*typelist)[2] may also be set to nill */
}

/* result must be free'd */
static char *concat_string_list (char **t, int *bytes)
{
    int l, n;
    char *s;
    for (l = n = 0;; n++) {
        if (!t[n])
            break;
        if (!t[n][0])
            break;
        l += strlen (t[n]) + 1;
    }
    s = malloc (l + 1);
    for (l = n = 0;; n++) {
        if (!t[n])
            break;
        if (!(t[n][0]))
            break;
        int t_size = strlen (t[n]) + 1;
        memcpy (s + l, t[n], t_size);
        l += t_size;
    }
    *bytes = l;
    s[l] = '\0';
    return s;
}

void xdnd_set_actions (DndClass * dnd, Window window, Atom * actions, char **descriptions)
{
    int n, l;
    char *s;
    n = array_length (actions);

    XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_ATOM, 32,
                     PropModeReplace, (unsigned char *) actions, n);

    s = concat_string_list (descriptions, &l);
    XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_STRING, 8,
                     PropModeReplace, (unsigned char *) s, l);
    xdnd_xfree (s);
}

/* returns 1 on error or no actions, otherwise result must be free'd
   xdnd_get_actions (window, &actions, &descriptions);
   free (actions); free (descriptions); */
int xdnd_get_actions (DndClass * dnd, Window window, Atom ** actions, char ***descriptions)
{
    Atom type, *a;
    int format, i;
    unsigned long count, dcount, remaining;
    unsigned char *data = 0, *r;

    *actions = 0;
    *descriptions = 0;
    XGetWindowProperty (dnd->display, window, dnd->XdndActionList,
                        0, 0x8000000L, False, XA_ATOM,
                        &type, &format, &count, &remaining, &data);

    if (type != XA_ATOM || format != 32 || count == 0 || !data) {
        if (data)
            XFree (data);
        return 1;
    }
    *actions = malloc ((count + 1) * sizeof (Atom));
    a = (Atom *) data;
    for (i = 0; i < count; i++)
        (*actions)[i] = a[i];
    (*actions)[count] = 0;

    XFree (data);

    data = 0;
    XGetWindowProperty (dnd->display, window, dnd->XdndActionDescription,
                        0, 0x8000000L, False, XA_STRING, &type, &format,
                        &dcount, &remaining, &data);

    if (type != XA_STRING || format != 8 || dcount == 0) {
        if (data)
            XFree (data);
        *descriptions = malloc ((count + 1) * sizeof (char *));
        dnd_warning ("XGetWindowProperty no property or wrong format for action descriptions");
        for (i = 0; i < count; i++)
            (*descriptions)[i] = "";
        (*descriptions)[count] = 0;
    } else {
        int l;
        l = (count + 1) * sizeof (char *);
        *descriptions = malloc (l + dcount);
        memcpy (*descriptions + l, data, dcount);
        XFree (data);
        data = (unsigned char *) *descriptions;
        data += l;
        l = 0;
        for (i = 0, r = data;; r += l + 1, i++) {
            l = strlen ((char *) r);
            if (!l || i >= count)
                break;
            (*descriptions)[i] = (char *) r;
        }
        for (; i < count; i++) {
            (*descriptions)[i] = "";
        }
        (*descriptions)[count] = 0;
    }
    return 0;
}

/* returns non-zero on cancel */
int xdnd_choose_action_dialog (DndClass * dnd, Atom * actions, char **descriptions, Atom * result)
{
    if (!actions[0])
        return 1;
    if (!dnd->action_choose_dialog) {        /* default to return the first action if no dialog set */
        *result = actions[0];
        return 0;
    }
    return (*dnd->action_choose_dialog) (dnd, descriptions, actions, result);
}

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_enter (DndClass * dnd, Window window, Window from, Atom * typelist)
{
    XEvent xevent;
    int n, i;
    n = array_length (typelist);

    memset (&xevent, 0, sizeof (xevent));

    xevent.xany.type = ClientMessage;
    xevent.xany.display = dnd->display;
    xevent.xclient.window = window;
    xevent.xclient.message_type = dnd->XdndEnter;
    xevent.xclient.format = 32;

    XDND_ENTER_SOURCE_WIN (&xevent) = from;
    XDND_ENTER_THREE_TYPES_SET (&xevent, n > XDND_THREE);
    XDND_ENTER_VERSION_SET (&xevent, dnd->version);
    for (i = 0; i < n && i < XDND_THREE; i++)
        XDND_ENTER_TYPE (&xevent, i) = typelist[i];
    xdnd_send_event (dnd, window, &xevent);
}

static void xdnd_send_position (DndClass * dnd, Window window, Window from, Atom action, int x, int y, unsigned long time)
{
    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->XdndPosition;
    xevent.xclient.format = 32;

    XDND_POSITION_SOURCE_WIN (&xevent) = from;
    XDND_POSITION_ROOT_SET (&xevent, x, y);
    if (dnd_version_at_least (dnd->dragging_version, 1))
        XDND_POSITION_TIME (&xevent) = time;
    if (dnd_version_at_least (dnd->dragging_version, 2))
        XDND_POSITION_ACTION (&xevent) = action;

    xdnd_send_event (dnd, window, &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);
}

static void xdnd_send_leave (DndClass * dnd, Window window, Window from)
{
    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->XdndLeave;
    xevent.xclient.format = 32;

    XDND_LEAVE_SOURCE_WIN (&xevent) = from;

    xdnd_send_event (dnd, window, &xevent);
}

static void xdnd_send_drop (DndClass * dnd, Window window, Window from, unsigned long time)
{
    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->XdndDrop;
    xevent.xclient.format = 32;

    XDND_DROP_SOURCE_WIN (&xevent) = from;
    if (dnd_version_at_least (dnd->dragging_version, 1))
        XDND_DROP_TIME (&xevent) = time;

    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)
{
    if (!(window = XGetSelectionOwner (dnd->display, dnd->XdndSelection))) {
        dnd_debug1 ("xdnd_convert_selection(): XGetSelectionOwner failed");
        return 1;
    }
    XConvertSelection (dnd->display, dnd->XdndSelection, type,
                    dnd->Xdnd_NON_PROTOCOL_ATOM, requester, CurrentTime);
    return 0;
}

/* returns non-zero on error */
static int xdnd_set_selection_owner (DndClass * dnd, Window window, Atom type, Time time)
{
    (void) type;

    if (!XSetSelectionOwner (dnd->display, dnd->XdndSelection, window, time)) {
        dnd_debug1 ("xdnd_set_selection_owner(): XSetSelectionOwner failed");
        return 1;
    }
    return 0;
}

static void xdnd_selection_send (DndClass * dnd, XSelectionRequestEvent * request, unsigned char *data, int length)
{
    XEvent xevent;
    dnd_debug2 ("      requestor = %ld", request->requestor);
    dnd_debug2 ("      property = %ld", request->property);
    dnd_debug2 ("      length = %d", length);
    XChangeProperty (dnd->display, request->requestor, request->property,
                     request->target, 8, PropModeReplace, data, length);
    xevent.xselection.type = SelectionNotify;
    xevent.xselection.property = request->property;
    xevent.xselection.display = request->display;
    xevent.xselection.requestor = request->requestor;
    xevent.xselection.selection = request->selection;
    xevent.xselection.target = request->target;
    xevent.xselection.time = request->time;
    xdnd_send_event (dnd, request->requestor, &xevent);
}

#if 0
/* respond to a notification that a primary selection has been sent */
int xdnd_get_selection (DndClass * dnd, Window from, Atom property, Window insert)
{
    long read;
    int error = 0;
    unsigned long remaining;
    if (!property)
        return 1;
    read = 0;
    do {
        unsigned char *s;
        Atom actual;
        int format;
        unsigned long count;
        if (XGetWindowProperty (dnd->display, insert, property, read / 4, 65536, 1,
                                AnyPropertyType, &actual, &format,
                                &count, &remaining,
                                &s) != Success) {
            XFree (s);
            return 1;
        }
        read += count;
        if (dnd->widget_insert_drop && !error)
            error = (*dnd->widget_insert_drop) (dnd, s, count, remaining, insert, from, actual);
        XFree (s);
    } while (remaining);
    return error;
}
#endif

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


int outside_rectangle (int x, int y, XRectangle * r)
{
    return (x < r->x || y < r->y || x >= r->x + r->width || y >= r->y + r->height);
}

/* avoids linking with the maths library */
static float xdnd_sqrt (float x)
{
    float last_ans, ans = 2, a;
    if (x <= 0.0f)
        return 0.0f;
    do {
        last_ans = ans;
        ans = (ans + x / ans) / 2;
        a = (ans - last_ans) / ans;
        if (a < 0.0f)
            a = (-a);
    } while (a > 0.001f);
    return ans;
}

/* returns action on success, 0 otherwise */
Atom xdnd_drag (DndClass * dnd, Window from, Atom action, Atom * typelist)
{
    XEvent xevent, xevent_temp;
    Window over_window = 0, last_window = 0;
#if XDND_VERSION >= 3
    Window last_dropper_toplevel = 0;
    int internal_dropable = 1;
#endif
    int n;
    DndCursor *cursor;
    float x_mouse, y_mouse;
    int result = 0, dnd_aware;

    if (!typelist)
        dnd_warning ("xdnd_drag() called with typelist = 0");

/* first wait until the mouse moves more than five pixels */
    do {
        XNextEvent (dnd->display, &xevent);
        if (xevent.type == ButtonRelease) {
            dnd_debug1 ("button release - no motion");
            XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
            return 0;
        }
    } while (xevent.type != MotionNotify);

    x_mouse = (float) xevent.xmotion.x_root;
    y_mouse = (float) xevent.xmotion.y_root;

    if (dnd->drag_threshold < 0.001f)
        dnd->drag_threshold = 4.0f;
    for (;;) {
        XNextEvent (dnd->display, &xevent);
        if (xevent.type == MotionNotify)
            if (xdnd_sqrt ((x_mouse - xevent.xmotion.x_root) * (x_mouse - xevent.xmotion.x_root) +
                           (y_mouse - xevent.xmotion.y_root) * (y_mouse - xevent.xmotion.y_root)) > dnd->drag_threshold)
                break;
        if (xevent.type == ButtonRelease) {
            XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
            return 0;
        }
    }

    dnd_debug1 ("moved 5 pixels - going to drag");

    n = array_length (typelist);
    if (n > XDND_THREE)
        xdnd_set_type_list (dnd, from, typelist);

    xdnd_reset (dnd);

    dnd->stage = XDND_DRAG_STAGE_DRAGGING;

    for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
        if (cursor->action == action)
            break;
    if (!cursor->width)
        cursor = &dnd->cursors[0];

/* the mouse has been dragged a little, so this is a drag proper */
    if (XGrabPointer (dnd->display, dnd->root_window, False,
                      ButtonMotionMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
                      GrabModeAsync, GrabModeAsync, None,
                      cursor->cursor, CurrentTime) != GrabSuccess)
        dnd_debug1 ("Unable to grab pointer");


    while (xevent.xany.type != ButtonRelease) {
        XAllowEvents (dnd->display, SyncPointer, CurrentTime);
        XNextEvent (dnd->display, &xevent);
        switch (xevent.type) {
        case Expose:
            if (dnd->handle_expose_events)
                (*dnd->handle_expose_events) (dnd, &xevent);
            break;
        case EnterNotify:
/* this event is not actually reported, so we find out by ourselves from motion events */
            break;
        case LeaveNotify:
/* this event is not actually reported, so we find out by ourselves from motion events */
            break;
        case ButtonRelease:
/* done, but must send a leave event */
            dnd_debug1 ("ButtonRelease - exiting event loop");
            break;
        case MotionNotify:
            dnd_aware = 0;
            dnd->dropper_toplevel = 0;
            memcpy (&xevent_temp, &xevent, sizeof (xevent));
            xevent.xmotion.subwindow = xevent.xmotion.window;
            {
                Window root_return, child_return;
                int x_temp, y_temp;
                unsigned int mask_return;
                while (XQueryPointer (dnd->display, xevent.xmotion.subwindow, &root_return, &child_return,
                                      &x_temp, &y_temp, &xevent.xmotion.x,
                                      &xevent.xmotion.y, &mask_return)) {
#if XDND_VERSION >= 3
                    if (!dnd_aware) {
                        if ((dnd_aware = xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))) {
                            dnd->dropper_toplevel = xevent.xmotion.subwindow;
                            xevent.xmotion.x_root = x_temp;
                            xevent.xmotion.y_root = y_temp;
                        }
                    }
#else
                    xevent.xmotion.x_root = x_temp;
                    xevent.xmotion.y_root = y_temp;
#endif
                    if (!child_return)
                        goto found_descendent;
                    xevent.xmotion.subwindow = child_return;
                }
                break;
            }
          found_descendent:

/* last_window is just for debug purposes */
            if (last_window != xevent.xmotion.subwindow) {
                dnd_debug2 ("window crossing to %ld", xevent.xmotion.subwindow);
                dnd_debug2 ("  current window is %ld", over_window);
                dnd_debug3 ("     last_window = %ld, xmotion.subwindow = %ld", last_window, xevent.xmotion.subwindow);
#if XDND_VERSION >= 3
                dnd_debug3 ("     dropper_toplevel = %ld, last_dropper_toplevel.subwindow = %ld", dnd->dropper_toplevel, last_dropper_toplevel);
#endif
                dnd_debug3 ("     dnd_aware = %d, dnd->options & XDND_OPTION_NO_HYSTERESIS = %ld", dnd_aware, (long) dnd->options & XDND_OPTION_NO_HYSTERESIS);
            }

#if XDND_VERSION < 3
/* is the new window dnd aware? if not stay in the old window */
            if (over_window != xevent.xmotion.subwindow &&
                last_window != xevent.xmotion.subwindow &&
                (
                    (dnd_aware = xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))
                    ||
                    (dnd->options & XDND_OPTION_NO_HYSTERESIS)
                ))
#else
            internal_dropable = 1;
            if (dnd->widget_exists && (*dnd->widget_exists) (dnd, xevent.xmotion.subwindow))
                if (!xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))
                    internal_dropable = 0;
            dnd_debug3 ("dnd->dropper_toplevel = %ld, last_dropper_toplevel = %ld\n", dnd->dropper_toplevel, last_dropper_toplevel);
            if ((dnd->dropper_toplevel != last_dropper_toplevel ||
                last_window != xevent.xmotion.subwindow) && internal_dropable &&
                (
                    (dnd_aware)
                    ||
                    (dnd->options & XDND_OPTION_NO_HYSTERESIS)
                ))
#endif
            {
/* leaving window we were over */
                if (over_window) {
                    if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
                        dnd_debug1 ("got leave at right stage");
                        dnd->stage = XDND_DRAG_STAGE_DRAGGING;
                        if (dnd->internal_drag) {
                            dnd_debug1 ("  our own widget");
                            if (dnd->widget_apply_leave)
                                (*dnd->widget_apply_leave) (dnd, over_window);
                        } else {
                            dnd_debug1 ("  not our widget - sending XdndLeave");
#if XDND_VERSION < 3
                            xdnd_send_leave (dnd, over_window, from);
#else
                            if (dnd->dropper_toplevel != last_dropper_toplevel) {
                                xdnd_send_leave (dnd, last_dropper_toplevel, from);
                            } else {
                                dnd_debug1 ("    not sending leave --> dnd->dropper_toplevel == last_dropper_toplevel");
                            }
#endif
                        }
                        dnd->internal_drag = 0;
                        dnd->dropper_window = 0;
                        dnd->ready_to_drop = 0;
                    } else {
                        dnd_debug1 ("got leave at wrong stage - ignoring");
                    }
                }
/* entering window we are currently over */
                over_window = xevent.xmotion.subwindow;
                if (dnd_aware) {
                    dnd_debug1 ("  is dnd aware");
                    dnd->stage = XDND_DRAG_STAGE_ENTERED;
                    if (dnd->widget_exists && (*dnd->widget_exists) (dnd, over_window))
                        dnd->internal_drag = 1;
                    if (dnd->internal_drag) {
                        dnd_debug1 ("    our own widget");
                    } else {
                        dnd_debug2 ("    not our widget - sending XdndEnter to %ld", over_window);
#if XDND_VERSION < 3
                        xdnd_send_enter (dnd, over_window, from, typelist);
#else
                        if (dnd->dropper_toplevel != last_dropper_toplevel)
                            xdnd_send_enter (dnd, dnd->dropper_toplevel, from, typelist);
#endif
                    }
                    dnd->want_position = 1;
                    dnd->ready_to_drop = 0;
                    dnd->rectangle.width = dnd->rectangle.height = 0;
                    dnd->dropper_window = over_window;
/* we want an additional motion event in case the pointer enters and then stops */
                    XSendEvent (dnd->display, from, 0, ButtonMotionMask, &xevent_temp);
                    XSync (dnd->display, 0);
                }
#if XDND_VERSION >= 3
                last_dropper_toplevel = dnd->dropper_toplevel;
#endif
/* we are now officially in a new window */
            } else {
/* got here, so we are just moving `inside' the same window */
                if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
                    dnd->supported_action = dnd->XdndActionCopy;
                    dnd_debug1 ("got motion at right stage");
                    dnd->x = xevent.xmotion.x_root;
                    dnd->y = xevent.xmotion.y_root;
                    if (dnd->want_position || outside_rectangle (dnd->x, dnd->y, &dnd->rectangle)) {
                        dnd_debug1 ("  want position and outside rectangle");
                        if (dnd->internal_drag) {
                            dnd_debug1 ("    our own widget");
                            dnd->ready_to_drop = (*dnd->widget_apply_position) (dnd, over_window, from,
                                                                                action, dnd->x, dnd->y, xevent.xmotion.time, typelist,
                                                                                &dnd->want_position, &dnd->supported_action, &dnd->desired_type, &dnd->rectangle);
                            /* if not ready, keep sending positions, this check is repeated below for XdndStatus from external widgets */
                            if (!dnd->ready_to_drop) {
                                dnd->want_position = 1;
                                dnd->rectangle.width = dnd->rectangle.height = 0;
                            }
                            dnd_debug2 ("      return action=%ld", dnd->supported_action);
                        } else {
#if XDND_VERSION < 3
                            dnd_debug3 ("    not our own widget - sending XdndPosition to %ld, action %ld", over_window, action);
                            xdnd_send_position (dnd, over_window, from, action, dnd->x, dnd->y, xevent.xmotion.time);
#else
                            dnd_debug3 ("    not our own widget - sending XdndPosition to %ld, action %ld", dnd->dropper_toplevel, action);
                            xdnd_send_position (dnd, dnd->dropper_toplevel, from, action, dnd->x, dnd->y, xevent.xmotion.time);
#endif
                        }
                    } else if (dnd->want_position) {
                        dnd_debug1 ("  inside rectangle");
                    } else {
                        dnd_debug1 ("  doesn't want position");
                    }
                }
            }
            last_window = xevent.xmotion.subwindow;
            break;
        case ClientMessage:
            dnd_debug1 ("ClientMessage recieved");
            if (xevent.xclient.message_type == dnd->XdndStatus && !dnd->internal_drag) {
                dnd_debug1 ("  XdndStatus recieved");
                if (dnd->stage == XDND_DRAG_STAGE_ENTERED
#if XDND_VERSION < 3
                        && XDND_STATUS_TARGET_WIN (&xevent) == dnd->dropper_window
#endif
                ) {
                    dnd_debug1 ("    XdndStatus stage correct, dropper window correct");
                    dnd->want_position = XDND_STATUS_WANT_POSITION (&xevent);
                    dnd->ready_to_drop = XDND_STATUS_WILL_ACCEPT (&xevent);
                    dnd->rectangle.x = XDND_STATUS_RECT_X (&xevent);
                    dnd->rectangle.y = XDND_STATUS_RECT_Y (&xevent);
                    dnd->rectangle.width = XDND_STATUS_RECT_WIDTH (&xevent);
                    dnd->rectangle.height = XDND_STATUS_RECT_HEIGHT (&xevent);
                    dnd->supported_action = dnd->XdndActionCopy;
                    if (dnd_version_at_least (dnd->dragging_version, 2))
                        dnd->supported_action = XDND_STATUS_ACTION (&xevent);
                    dnd_debug3 ("      return action=%ld, ready=%d", dnd->supported_action, dnd->ready_to_drop);
                    /* if not ready, keep sending positions, this check is repeated above for internal widgets */
                    if (!dnd->ready_to_drop) {
                        dnd->want_position = 1;
                        dnd->rectangle.width = dnd->rectangle.height = 0;
                    }
                    dnd_debug3 ("      rectangle = (x=%d, y=%d, ", dnd->rectangle.x, dnd->rectangle.y);
                    dnd_debug4                               ("w=%d, h=%d), want_position=%d\n", dnd->rectangle.width, dnd->rectangle.height, dnd->want_position);
                }
#if XDND_VERSION < 3
                else if (XDND_STATUS_TARGET_WIN (&xevent) != dnd->dropper_window) {
                    dnd_debug3 ("    XdndStatus XDND_STATUS_TARGET_WIN (&xevent) = %ld, dnd->dropper_window = %ld", XDND_STATUS_TARGET_WIN (&xevent), dnd->dropper_window);
                }
#endif
                else {
                    dnd_debug2 ("    XdndStatus stage incorrect dnd->stage = %d", dnd->stage);
                }
            }
            break;
        case SelectionRequest:{
/* the target widget MAY request data, so wait for SelectionRequest */
                int length = 0;
                unsigned char *data = 0;
                dnd_debug1 ("SelectionRequest - getting widget data");

                (*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
                if (data) {
                    dnd_debug1 ("  sending selection");
                    xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
                    xdnd_xfree (data);
                }
            }
            break;
        }
    }

    if (dnd->ready_to_drop) {
        Time time;
        dnd_debug1 ("ready_to_drop - sending XdndDrop");
        time = xevent.xbutton.time;
        if (dnd->internal_drag) {
/* we are dealing with our own widget, no need to send drop events, just put the data straight */
            int length = 0;
            unsigned char *data = 0;
            if (dnd->widget_insert_drop) {
                (*dnd->widget_get_data) (dnd, from, &data, &length, dnd->desired_type);
                if (data) {
                    if (!(*dnd->widget_insert_drop) (dnd, data, length, 0, dnd->dropper_window, from, dnd->desired_type)) {
                        result = dnd->supported_action;                /* success - so return action to caller */
                        dnd_debug1 ("  inserted data into widget - success");
                    } else {
                        dnd_debug1 ("  inserted data into widget - failed");
                    }
                    xdnd_xfree (data);
                } else {
                    dnd_debug1 ("  got data from widget, but data is null");
                }
            }
        } else {
            xdnd_set_selection_owner (dnd, from, dnd->desired_type, time);
#if XDND_VERSION < 3
            xdnd_send_drop (dnd, dnd->dropper_window, from, time);
#else
            xdnd_send_drop (dnd, dnd->dropper_toplevel, from, time);
#endif
        }
        if (!dnd->internal_drag)
            for (;;) {
                XAllowEvents (dnd->display, SyncPointer, CurrentTime);
                XNextEvent (dnd->display, &xevent);
                if (xevent.type == ClientMessage && xevent.xclient.message_type == dnd->XdndFinished) {
                    dnd_debug1 ("XdndFinished");
#if XDND_VERSION < 3
                    if (XDND_FINISHED_TARGET_WIN (&xevent) == dnd->dropper_window) {
#endif
                        dnd_debug2 ("  source correct - exiting event loop, action=%ld", dnd->supported_action);
                        result = dnd->supported_action;                /* success - so return action to caller */
                        break;
#if XDND_VERSION < 3
                    }
#endif
                } else if (xevent.type == Expose) {
                    if (dnd->handle_expose_events)
                        (*dnd->handle_expose_events) (dnd, &xevent);
                } else if (xevent.type == MotionNotify) {
                    if (xevent.xmotion.time > time + (dnd->time_out ? dnd->time_out * 1000 : 10000)) {        /* allow a ten second timeout as default */
                        dnd_debug1 ("timeout - exiting event loop");
                        break;
                    }
                } else if (xevent.type == SelectionRequest && xevent.xselectionrequest.selection == dnd->XdndSelection) {
/* the target widget is going to request data, so check for SelectionRequest events */
                    int length = 0;
                    unsigned char *data = 0;

                    dnd_debug1 ("SelectionRequest - getting widget data");
                    (*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
                    if (data) {
                        dnd_debug1 ("  sending selection");
                        xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
                        xdnd_xfree (data);
                    }
/* don't wait for a XdndFinished event */
                    if (!dnd_version_at_least (dnd->dragging_version, 2))
                        break;
                }
            }
    } else {
        dnd_debug1 ("not ready_to_drop - ungrabbing pointer");
    }
    XUngrabPointer (dnd->display, CurrentTime);
    xdnd_reset (dnd);
    return result;
}

/* 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;
}

/*
   Following here is a sample implementation: Suppose we want a window
   to recieve drops, but do not want to be concerned with setting up all
   the DndClass methods. All we then do is call xdnd_get_drop() whenever a
   ClientMessage is recieved. If the message has nothing to do with XDND,
   xdnd_get_drop quickly returns 0. If it is a XdndEnter message, then
   xdnd_get_drop enters its own XNextEvent loop and handles all XDND
   protocol messages internally, returning the action requested.

   You should pass a desired typelist and actionlist to xdnd_get_type.
   These must be null terminated arrays of atoms, or a null pointer
   if you would like any action or type to be accepted. If typelist
   is null then the first type of the dragging widgets typelist will
   be the one used. If actionlist is null, then only XdndActionCopy will
   be accepted.

   The result is stored in *data, length, type, x and y.
   *data must be free'd.
 */

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, Atom * typelist, Atom * actionlist,
          unsigned char **data, int *length, Atom * type, int *x, int *y)
{
    Atom action = 0;
    static int initialised = 0;
    static DndClass dnd;
    if (!initialised) {
        xdnd_init (&dnd, display);
        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.actionlist = actionlist;
        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) {
            *length = i.drop_data_length;
            *data = i.drop_data;
            action = i.return_action;
            *type = i.return_type;
            *x = i.x;
            *y = i.y;
        }
    }
    return action;
}
