Drag-n-drop support on Linux
authorSergey Sharybin <sergey.vfx@gmail.com>
Fri, 17 Feb 2012 16:58:09 +0000 (16:58 +0000)
committerSergey Sharybin <sergey.vfx@gmail.com>
Fri, 17 Feb 2012 16:58:09 +0000 (16:58 +0000)
This commit implements drag-n-drop support from external applications into Blender.
Used xdnd implementation from Paul Sheer.

15 files changed:
extern/CMakeLists.txt
extern/SConscript
extern/xdnd/CMakeLists.txt [new file with mode: 0644]
extern/xdnd/SConscript [new file with mode: 0644]
extern/xdnd/xdnd.c [new file with mode: 0644]
extern/xdnd/xdnd.h [new file with mode: 0644]
intern/ghost/CMakeLists.txt
intern/ghost/SConscript
intern/ghost/intern/GHOST_DropTargetX11.cpp [new file with mode: 0644]
intern/ghost/intern/GHOST_DropTargetX11.h [new file with mode: 0644]
intern/ghost/intern/GHOST_SystemX11.cpp
intern/ghost/intern/GHOST_SystemX11.h
intern/ghost/intern/GHOST_WindowX11.cpp
intern/ghost/intern/GHOST_WindowX11.h
source/creator/CMakeLists.txt

index 8442e5d0c712939164db0f3a0cad74e69db4d871..5b2639ecb0ddbab34e6f4e05c3e4f1599a30f4d1 100644 (file)
@@ -71,3 +71,7 @@ endif()
 if(WITH_CARVE)
        add_subdirectory(carve)
 endif()
+
+if(UNIX AND NOT APPLE)
+       add_subdirectory(xdnd)
+endif()
index 738342b3dcc24eae8c5d92f13dcd021f94c2f03d..5ecf9b1b2afc79fda6de64ac463289ef2e7007d8 100644 (file)
@@ -34,3 +34,6 @@ if env['WITH_BF_LIBMV']:
 
 if env['WITH_BF_CARVE']:
     SConscript(['carve/SConscript'])
+
+if env['OURPLATFORM'] in ('linux', 'openbsd3', 'sunos5', 'freebsd7', 'freebsd8', 'freebsd9', 'aix4', 'aix5'):
+    SConscript(['xdnd/SConscript'])
diff --git a/extern/xdnd/CMakeLists.txt b/extern/xdnd/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f7eded7
--- /dev/null
@@ -0,0 +1,43 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# The Original Code is Copyright (C) 2012, Blender Foundation
+# All rights reserved.
+#
+# The Original Code is: all of this file.
+#
+# Contributor(s): Sergey Sharybin.
+#
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+       .
+)
+
+set(INC_SYS
+
+)
+
+set(SRC
+       xdnd.c
+       xdnd.h
+)
+
+add_definitions(
+       -DHAVE_SYS_TIME_H
+)
+
+blender_add_lib(extern_xdnd "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/extern/xdnd/SConscript b/extern/xdnd/SConscript
new file mode 100644 (file)
index 0000000..6f1fe72
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+Import('env')
+
+defs = ['HAVE_SYS_TIME_H']
+sources = env.Glob('*.c')
+
+incs = '.'
+
+env.BlenderLib ( 'extern_xdnd', sources, Split(incs), defs, libtype=['extern','player'], priority=[10, 185])
diff --git a/extern/xdnd/xdnd.c b/extern/xdnd/xdnd.c
new file mode 100644 (file)
index 0000000..9bdee89
--- /dev/null
@@ -0,0 +1,1599 @@
+/* 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>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#   include <unistd.h>
+#endif
+
+#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)       
+#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))
+
+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}
+};
+
+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)
+        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;
+    *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;
+        strcpy (s + l, t[n]);
+        l += strlen (t[n]) + 1;
+    }
+    *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;
+
+    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)
+{
+    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.0)
+        return 0.0;
+    do {
+        last_ans = ans;
+        ans = (ans + x / ans) / 2;
+        a = (ans - last_ans) / ans;
+        if (a < 0.0)
+            a = (-a);
+    } while (a > 0.001);
+    return ans;
+}
+
+#define print_marks print_win_marks(from,__FILE__,__LINE__);
+
+/* 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)
+        dnd->drag_threshold = 4.0;
+    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;
+    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;
+
+    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;
+}
+
+
diff --git a/extern/xdnd/xdnd.h b/extern/xdnd/xdnd.h
new file mode 100644 (file)
index 0000000..c903b51
--- /dev/null
@@ -0,0 +1,221 @@
+/* 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.
+ */
+
+#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 2 */
+#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])
+
+struct _DndCursor {
+    int width, height;
+    int x, y;
+    unsigned char *image_data, *mask_data;
+    char *_action;
+    Pixmap image_pixmap, mask_pixmap;
+    Cursor cursor;
+    Atom action;
+};
+
+typedef struct _DndCursor DndCursor;
+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);
+
+/* In response to DELETE requests : FIXME - not yet used */
+    int (*widget_delete_selection) (DndClass * dnd, Window window, Window from);
+
+/* returns 1 if widget exists, zero otherwise. If this method is not 
+   set then the code assumes that no widgets have support for recieving drops.
+   In this case none of the widget methods need be set. */
+    int (*widget_exists) (DndClass * dnd, Window window);
+
+/* 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);
+
+/* returns drag data of the specified type. This will be one of `typelist' given to xdnd_drag */
+    void (*widget_get_data) (DndClass * dnd, Window window, unsigned char **data, int *length, Atom type);
+
+/* this is called from with the main event loop if an expose event is recieved and is optional */
+    void (*handle_expose_events) (DndClass * dnd, XEvent * xevent);
+
+/* creates a chooser dialog if the action is XdndActionAsk. Returns non-zero on cancel */
+    int (*action_choose_dialog) (DndClass * dnd, char **descriptions, Atom * actions, Atom * result);
+
+#if 0 /* implemented internally */
+/* returns a widget that is dnd aware within a parent widget that lies under the point x, y */
+    Window (*widget_get_child_widget) (DndClass * dnd, Window parent, int x, int y);
+#endif
+
+    void *pad1[8];
+
+    DndCursor *cursors;
+
+    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 xdnd_init (DndClass * dnd, Display * display);
+void xdnd_shut (DndClass * dnd);
+/* for nested widgets where parent and child receive drops of different
+types; then always pass typelist as null */
+void xdnd_set_dnd_aware (DndClass * dnd, Window window, Atom * typelist);
+int xdnd_is_dnd_aware (DndClass * dnd, Window window, int *version, Atom * typelist);
+void xdnd_set_type_list (DndClass * dnd, Window window, Atom * typelist);
+void xdnd_set_actions (DndClass * dnd, Window window, Atom * actions, char **descriptions);
+int xdnd_get_actions (DndClass * dnd, Window window, Atom ** actions, char ***descriptions);
+int xdnd_choose_action_dialog (DndClass * dnd, Atom * actions, char **descriptions, Atom * result);
+Atom xdnd_drag (DndClass * dnd, Window from, Atom action, Atom * typelist);
+
+/* 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, Atom * typelist, Atom * actionlist,
+                    unsigned char **data, int *length, Atom * type, int *x, int *y);
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif         /* !_X_DND_H */
+
+
index 35b617e5452a34333bd3aad1022b3ce450319918..3d65f4972c40cfab7c361d3125aa667f65898261 100644 (file)
@@ -220,11 +220,17 @@ elseif(UNIX)
                intern/GHOST_SystemX11.cpp
                intern/GHOST_SystemPathsX11.cpp
                intern/GHOST_WindowX11.cpp
+               intern/GHOST_DropTargetX11.cpp
 
                intern/GHOST_DisplayManagerX11.h
                intern/GHOST_SystemX11.h
                intern/GHOST_SystemPathsX11.h
                intern/GHOST_WindowX11.h
+               intern/GHOST_DropTargetX11.h
+       )
+
+       list(APPEND INC
+               ../../extern/xdnd
        )
 
        if(X11_XF86keysym_INCLUDE_PATH)
index 68a0cbf7bdd02c87331bfec0736c76ed3b346117..d83107717fcf481578f88bbff524052cbf27bacd 100644 (file)
@@ -42,6 +42,8 @@ elif window_system in ('linux', 'openbsd3', 'sunos5', 'freebsd7', 'freebsd8', 'f
     # defs += ['PREFIX=\\"/usr/local/\\"']  # XXX, make an option
     defs += ['WITH_X11_XINPUT']  # XXX, make an option
 
+    incs += ' #/extern/xdnd'
+
 elif window_system in ('win32-vc', 'win32-mingw', 'cygwin', 'linuxcross', 'win64-vc'):
     for f in pf:
         try:
diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp
new file mode 100644 (file)
index 0000000..2239ac6
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2012 by the Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Sergey Sharybin.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file ghost/intern/GHOST_DropTargetX11.cpp
+ *  \ingroup GHOST
+ */
+
+#include "GHOST_DropTargetX11.h"
+#include "GHOST_Debug.h"
+
+#include <ctype.h>
+#include <assert.h>
+
+bool GHOST_DropTargetX11::m_xdndInitialized = false;
+DndClass GHOST_DropTargetX11::m_dndClass;
+Atom * GHOST_DropTargetX11::m_dndTypes = NULL;
+Atom * GHOST_DropTargetX11::m_dndActions = NULL;
+const char *GHOST_DropTargetX11::m_dndMimeTypes[] = {"url/url", "text/uri-list", "text/plain", "application/octet-stream"};
+int GHOST_DropTargetX11::m_refCounter = 0;
+
+#define dndTypeURLID           0
+#define dndTypeURIListID       1
+#define dndTypePlainTextID     2
+#define dndTypeOctetStreamID   3
+
+#define dndTypeURL          m_dndTypes[dndTypeURLID]
+#define dndTypeURIList      m_dndTypes[dndTypeURIListID]
+#define dndTypePlainText    m_dndTypes[dndTypePlainTextID]
+#define dndTypeOctetStream  m_dndTypes[dndTypeOctetStreamID]
+
+void GHOST_DropTargetX11::Initialize(void)
+{
+       Display *display = m_system->getXDisplay();
+       int dndTypesCount = sizeof(m_dndMimeTypes) / sizeof(char*);
+       int counter;
+
+       xdnd_init(&m_dndClass, display);
+
+       m_dndTypes = new Atom[dndTypesCount + 1];
+       XInternAtoms(display, (char**)m_dndMimeTypes, dndTypesCount, 0, m_dndTypes);
+       m_dndTypes[dndTypesCount] = 0;
+
+       m_dndActions = new Atom[8];
+       counter = 0;
+
+       m_dndActions[counter++] = m_dndClass.XdndActionCopy;
+       m_dndActions[counter++] = m_dndClass.XdndActionMove;
+
+#if 0 /* Not supported yet */
+       dndActions[counter++] = dnd->XdndActionLink;
+       dndActions[counter++] = dnd->XdndActionAsk;
+       dndActions[counter++] = dnd->XdndActionPrivate;
+       dndActions[counter++] = dnd->XdndActionList;
+       dndActions[counter++] = dnd->XdndActionDescription;
+#endif
+
+       m_dndActions[counter++] = 0;
+}
+
+void GHOST_DropTargetX11::Uninitialize(void)
+{
+       xdnd_shut(&m_dndClass);
+}
+
+GHOST_DropTargetX11::GHOST_DropTargetX11(GHOST_WindowX11 * window, GHOST_SystemX11 * system)
+:
+m_window(window),
+m_system(system)
+{
+       if (!m_xdndInitialized) {
+               Initialize();
+               m_xdndInitialized = true;
+               GHOST_PRINT("XDND initialized\n");
+       }
+
+       Window wnd = window->getXWindow();
+
+       xdnd_set_dnd_aware(&m_dndClass, wnd, 0);
+       xdnd_set_type_list(&m_dndClass, wnd, m_dndTypes);
+
+       m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
+       m_refCounter++;
+}
+
+GHOST_DropTargetX11::~GHOST_DropTargetX11()
+{
+       m_refCounter--;
+       if (m_refCounter == 0) {
+               Uninitialize();
+               m_xdndInitialized = false;
+               GHOST_PRINT("XDND uninitialized\n");
+       }
+}
+
+/* based on a code from Saul Rennison
+ * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */
+
+typedef enum DecodeState_e {
+       STATE_SEARCH = 0, ///< searching for an ampersand to convert
+       STATE_CONVERTING  ///< convert the two proceeding characters from hex
+} DecodeState_e;
+
+void GHOST_DropTargetX11::UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn)
+{
+       unsigned int i;
+       unsigned int len = strlen(encodedIn);
+       DecodeState_e state = STATE_SEARCH;
+       int j, asciiCharacter;
+       char tempNumBuf[3] = {0};
+       bool bothDigits = true;
+
+       memset(decodedOut, 0, bufferSize);
+
+       for (i = 0; i < len; ++i) {
+               switch (state) {
+                       case STATE_SEARCH:
+                               if (encodedIn[i] != '%') {
+                                       strncat(decodedOut, &encodedIn[i], 1);
+                                       assert(strlen(decodedOut) < bufferSize);
+                                       break;
+                               }
+
+                               // We are now converting
+                               state = STATE_CONVERTING;
+                               break;
+
+                       case STATE_CONVERTING:
+                               bothDigits = true;
+
+                               // Create a buffer to hold the hex. For example, if %20, this
+                               // buffer would hold 20 (in ASCII)
+                               memset(tempNumBuf, 0, sizeof(tempNumBuf));
+
+                               // Conversion complete (i.e. don't convert again next iter)
+                               state = STATE_SEARCH;
+
+                               strncpy(tempNumBuf, &encodedIn[i], 2);
+
+                               // Ensure both characters are hexadecimal
+
+                               for (j = 0; j < 2; ++j) {
+                                       if (!isxdigit(tempNumBuf[j]))
+                                               bothDigits = false;
+                               }
+
+                               if (!bothDigits)
+                                       break;
+
+                               // Convert two hexadecimal characters into one character
+                               sscanf(tempNumBuf, "%x", &asciiCharacter);
+
+                               // Ensure we aren't going to overflow
+                               assert(strlen(decodedOut) < bufferSize);
+
+                               // Concatenate this character onto the output
+                               strncat(decodedOut, (char*)&asciiCharacter, 1);
+
+                               // Skip the next character
+                               i++;
+                               break;
+               }
+       }
+}
+
+char *GHOST_DropTargetX11::FileUrlDecode(char *fileUrl)
+{
+       if(!strncpy(fileUrl, "file://", 7) == 0) {
+               /* assume one character of encoded URL can be expanded to 4 chars max */
+               int decodedSize = 4 * strlen(fileUrl) + 1;
+               char *decodedPath = (char *)malloc(decodedSize);
+
+               UrlDecode(decodedPath, decodedSize, fileUrl + 7);
+
+               return decodedPath;
+       }
+
+       return NULL;
+}
+
+void *GHOST_DropTargetX11::getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize)
+{
+       GHOST_TStringArray *strArray = NULL;
+       int totPaths = 0, curLength = 0;
+
+       /* count total number of file pathes in buffer */
+       for (int i = 0; i <= dropBufferSize; i++) {
+               if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
+                       if (curLength) {
+                               totPaths++;
+                               curLength = 0;
+                       }
+               }
+               else curLength++;
+       }
+
+       strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
+       strArray->count = 0;
+       strArray->strings = (GHOST_TUns8**)malloc(totPaths*sizeof(GHOST_TUns8*));
+
+       curLength = 0;
+       for (int i = 0; i <= dropBufferSize; i++) {
+               if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
+                       if (curLength) {
+                               char *curPath = (char *)malloc(curLength + 1);
+                               char *decodedPath;
+
+                               strncpy(curPath, (char*)dropBuffer + i - curLength, curLength);
+                               curPath[curLength] = 0;
+
+                               decodedPath = FileUrlDecode(curPath);
+                               if(decodedPath) {
+                                       strArray->strings[strArray->count] = (GHOST_TUns8*)decodedPath;
+                                       strArray->count++;
+                               }
+
+                               free(curPath);
+                               curLength = 0;
+                       }
+               }
+               else curLength++;
+       }
+
+       return strArray;
+}
+
+void *GHOST_DropTargetX11::getGhostData(Atom dropType, unsigned char *dropBuffer, int dropBufferSize)
+{
+       void *data = NULL;
+       unsigned char *tmpBuffer = (unsigned char *)malloc(dropBufferSize + 1);
+       bool needsFree = true;
+
+       /* ensure NULL-terminator */
+       memcpy(tmpBuffer, dropBuffer, dropBufferSize);
+       tmpBuffer[dropBufferSize] = 0;
+
+       if (dropType == dndTypeURIList) {
+               m_draggedObjectType = GHOST_kDragnDropTypeFilenames;
+               data = getURIListGhostData(tmpBuffer, dropBufferSize);
+       }
+       else if (dropType == dndTypeURL) {
+               /* need to be tested */
+               char *decodedPath = FileUrlDecode((char *)tmpBuffer);
+
+               if (decodedPath) {
+                       m_draggedObjectType = GHOST_kDragnDropTypeString;
+                       data = decodedPath;
+               }
+       }
+       else if (dropType == dndTypePlainText || dropType == dndTypeOctetStream) {
+               m_draggedObjectType = GHOST_kDragnDropTypeString;
+               data = tmpBuffer;
+               needsFree = false;
+       }
+       else {
+               m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
+       }
+
+       if (needsFree)
+               free(tmpBuffer);
+
+       return data;
+}
+
+bool GHOST_DropTargetX11::GHOST_HandleClientMessage(XEvent *event)
+{
+       Atom dropType;
+       unsigned char *dropBuffer;
+       int dropBufferSize, dropX, dropY;
+
+       if (xdnd_get_drop(m_system->getXDisplay(), event, m_dndTypes, m_dndActions,
+                         &dropBuffer, &dropBufferSize, &dropType, &dropX, &dropY))
+       {
+               void *data = getGhostData(dropType, dropBuffer, dropBufferSize);
+
+               if (data)
+                       m_system->pushDragDropEvent(GHOST_kEventDraggingDropDone, m_draggedObjectType, m_window, dropX, dropY, data);
+
+               free(dropBuffer);
+
+               m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
+
+               return true;
+       }
+
+       return false;
+}
diff --git a/intern/ghost/intern/GHOST_DropTargetX11.h b/intern/ghost/intern/GHOST_DropTargetX11.h
new file mode 100644 (file)
index 0000000..2b08b7e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2012 by the Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Sergey Sharybin.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file ghost/intern/GHOST_DropTargetWin32.h
+ *  \ingroup GHOST
+ */
+
+#ifndef _GHOST_DROP_TARGET_X11_H_
+#define _GHOST_DROP_TARGET_X11_H_
+
+#include <GHOST_Types.h>
+#include "GHOST_WindowX11.h"
+#include "GHOST_SystemX11.h"
+
+#include "xdnd.h"
+
+class GHOST_DropTargetX11
+{
+public:
+       /**
+        * Constructor
+        *
+        * @param window        The window to register as drop target.
+        * @param system        The associated system.
+        */
+       GHOST_DropTargetX11(GHOST_WindowX11 * window, GHOST_SystemX11 * system);
+
+       /**
+        * Destructor
+        */
+       ~GHOST_DropTargetX11();
+
+       /**
+         * Handler of ClientMessage X11 event
+         */
+       bool GHOST_HandleClientMessage(XEvent *event);
+
+       /**
+        * Get data to pass in event.
+        * It checks the type and calls specific functions for each type.
+        * @param dropType - type of dropped entity.
+        * @param dropBuffer - buffer returned from source application
+        * @param dropBufferSize - size of returned buffer
+        * @return Pointer to data.
+        */
+       void *getGhostData(Atom dropType, unsigned char *dropBuffer, int dropBufferSize);
+
+private:
+       /* Internal helper functions */
+
+       /**
+         * Initiailize XDND and all related X atoms
+         */
+       void Initialize(void);
+
+       /**
+         * Uninitiailize XDND and all related X atoms
+         */
+       void Uninitialize(void);
+
+       /**
+         * Get data to be passed to event from text/uri-list mime type
+         * @param dropBuffer - buffer returned from source application
+         * @param dropBufferSize - size of dropped buffer
+         * @return pointer to newly created GHOST data
+         */
+       void * getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize);
+
+       /**
+         * Decode URL (i.e. converts "file:///a%20b/test" to "file:///a b/test")
+         * @param decodedOut - buffer for decoded URL
+         * @param bufferSize - size of output buffer
+         * @param encodedIn - input encoded buffer to be decoded
+         */
+       void UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn);
+
+       /**
+         * Fully decode file URL (i.e. converts "file:///a%20b/test" to "/a b/test")
+         * @param fileUrl - file path URL to be fully decoded
+         * @return decoded file path (resutl shold be free-d)
+         */
+       char *FileUrlDecode(char *fileUrl);
+
+       /* The associated GHOST_WindowWin32. */
+       GHOST_WindowX11 * m_window;
+       /* The System. */
+       GHOST_SystemX11 * m_system;
+
+       /* Data type of the dragged object */
+       GHOST_TDragnDropTypes m_draggedObjectType;
+
+       /* is dnd stuff initialzied */
+       static bool m_xdndInitialized;
+
+       /* class holding internal stiff of xdnd library */
+       static DndClass m_dndClass;
+
+       /* list of supported types to eb draggeg into */
+       static Atom * m_dndTypes;
+
+       /* list of supported dran'n'drop actions */
+       static Atom * m_dndActions;
+
+       /* List of supported MIME types to be dragged into */
+       static const char *m_dndMimeTypes[];
+
+       /* counter of references to global XDND structures */
+       static int m_refCounter;
+};
+
+#endif  // _GHOST_DROP_TARGET_X11_H_
index 22c16009591840b09ec591386b270d4f6e866e41..7261770771ad04c7fac3cdde7342f7dcf59c1ed6 100644 (file)
@@ -42,6 +42,8 @@
 #include "GHOST_EventButton.h"
 #include "GHOST_EventWheel.h"
 #include "GHOST_DisplayManagerX11.h"
+#include "GHOST_DropTargetX11.h"
+#include "GHOST_EventDragnDrop.h"
 #ifdef WITH_INPUT_NDOF
 #include "GHOST_NDOFManagerX11.h"
 #endif
@@ -709,8 +711,12 @@ GHOST_SystemX11::processEvent(XEvent *xe)
                                        }
                                }
                        } else {
-                               /* Unknown client message, ignore */
+                               /* try to handle drag event (if there's no such events, GHOST_HandleClientMessage will return zero) */
+                               if (window->getDropTarget()->GHOST_HandleClientMessage(xe) == false) {
+                                       /* Unknown client message, ignore */
+                               }
                        }
+
                        break;
                }
                
@@ -1478,3 +1484,17 @@ void GHOST_SystemX11::putClipboard(GHOST_TInt8 *buffer, bool selection) const
                        fprintf(stderr, "failed to own primary\n");
        }
 }
+
+GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType, 
+                                                                                                       GHOST_TDragnDropTypes draggedObjectType,
+                                                                                                       GHOST_IWindow* window,
+                                                                                                       int mouseX, int mouseY,
+                                                                                                       void* data)
+{
+       GHOST_SystemX11* system = ((GHOST_SystemX11*)getSystem());
+       return system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(),
+                                                                                                         eventType,
+                                                                                                         draggedObjectType,
+                                                                                                         window,mouseX,mouseY,data)
+                       );
+}
index 73d9c95e273309761ebce5bc947862125935d9d4..8e9131877d3e3edd6447523b8810f7c543e5a26b 100644 (file)
@@ -235,6 +235,18 @@ public:
         */
        void putClipboard(GHOST_TInt8 *buffer, bool selection) const;
 
+       /**
+        * Creates a drag'n'drop event and pushes it immediately onto the event queue. 
+        * Called by GHOST_DropTargetX11 class.
+        * @param eventType The type of drag'n'drop event
+        * @param draggedObjectType The type object concerned (currently array of file names, string, ?bitmap)
+        * @param mouseX x mouse coordinate (in window coordinates)
+        * @param mouseY y mouse coordinate
+        * @param window The window on which the event occurred
+        * @return Indication whether the event was handled. 
+        */
+       static GHOST_TSuccess pushDragDropEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,GHOST_IWindow* window, int mouseX, int mouseY, void* data);
+
        /**
         * @see GHOST_ISystem
         */
index 71e3f7b334037c7dc4e2678174913a651719dbae..ea99a9ea7b482cb9cae48e06a1d3574d00b15b15 100644 (file)
@@ -32,6 +32,7 @@
 
 #include "GHOST_WindowX11.h"
 #include "GHOST_SystemX11.h"
+#include "GHOST_DropTargetX11.h"
 #include "STR_String.h"
 #include "GHOST_Debug.h"
 
@@ -326,6 +327,10 @@ GHOST_WindowX11(
                
        }       
        
+       /* initialize drop target for newly created window */
+       m_dropTarget = new GHOST_DropTargetX11(this, m_system);
+       GHOST_PRINT("Set drop target\n");
+
        /*
         * One of the problem with WM-spec is that can't set a property
         * to a window that isn't mapped. That is why we can't "just
@@ -1318,6 +1323,7 @@ GHOST_WindowX11::
        }
 #endif
 
+       delete m_dropTarget;
 
        XDestroyWindow(m_display, m_window);
        XFree(m_visual);
index f1146db50f8d7e9daa8e297516adc20d680e0964..f5cbceff7a41c0245ea47b5a7ca2e24e34d230a9 100644 (file)
@@ -45,6 +45,7 @@
 
 class STR_String;
 class GHOST_SystemX11;
+class GHOST_DropTargetX11;
 
 /**
  * X11 implementation of GHOST_IWindow.
@@ -224,6 +225,9 @@ public:
        XIC getX11_XIC() { return m_xic; }
 #endif
 
+       GHOST_DropTargetX11* getDropTarget()
+       { return m_dropTarget; }
+
        /*
         * Need this in case that we want start the window
         * in FullScree or Maximized state.
@@ -361,6 +365,8 @@ private :
        /** Cache of XC_* ID's to XCursor structures */
        std::map<unsigned int, Cursor> m_standard_cursors;
 
+       GHOST_DropTargetX11 * m_dropTarget;
+
 #ifdef WITH_X11_XINPUT
        /* Tablet devices */
        XTablet m_xtablet;
index 1b4d88a6f2e90fdd1726cec606d3bc45973ed1f9..6947f88d2899842e937b68fec6166fbe322e8575 100644 (file)
@@ -828,6 +828,7 @@ endif()
                extern_minilzo
                extern_lzma
                extern_colamd
+               extern_xdnd
                ge_logic_ketsji
                extern_recastnavigation
                ge_phys_common