1 /*
2  * LessTif ID : $Id: xdnd.c,v 1.1 2004/08/28 19:22:46 dannybackx Exp $
3  *
4  * This file is maintained by Paul Sheer.
5  * Integration with LessTif should be outside this source, so Paul can
6  * maintain this file and guarantee that other users (GTK+, ...) use
7  * the same version.
8  *
9  * If the integration is not possible without changing this file,
10  * please contact Paul.
11  */
12 /* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol
13    Copyright (C) 1998  Paul Sheer
14 
15    This library is free software; you can redistribute it and/or
16    modify it under the terms of the GNU Library General Public
17    License as published by the Free Software Foundation; either
18    version 2 of the License, or (at your option) any later version.
19 
20    This library is distributed in the hope that it will be useful,
21    but WITHOUT ANY WARRANTY; without even the implied warranty of
22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23    Library General Public License for more details.
24 
25    You should have received a copy of the GNU Library General Public
26    License along with this library; if not, write to the Free
27    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 
29    Further info can also be obtained by emailing the author at,
30        psheer@obsidian.co.za
31  */
32 
33 
34 /*
35    Released 1998-08-07
36    Changes:
37 
38 
39 */
40 
41 /*
42     TODO:
43      - action_choose_dialog not yet supported (never called)
44      - widget_delete_selection not yet supported and DELETE requests are ignored
45      - not yet tested with applications that only supported XDND 0 or 1
46      - INCR protocol not yet implemented
47 */
48 
49 #include <LTconfig.h>
50 
51 #include <string.h>
52 #include <stdlib.h>
53 #include <stdio.h>
54 
55 #include <X11/Xlib.h>
56 #include <X11/X.h>
57 #include <X11/Xatom.h>
58 #include <Xm/XmP.h>
59 #include <XmI/xdnd.h>
60 
61 #include <XmI/DebugUtil.h>
62 
63 /* #include "mad.h" */
64 
65 /* just to remind us : */
66 
67 #if 0
68 typedef struct {
69     int type;
70     unsigned long serial;
71     Bool send_event;
72     Display *display;
73     Window window;
74     Atom message_type;
75     int format;
76     union {
77 	char b[20];
78 	short s[10];
79 	long l[5];
80     } data;
81 } XClientMessageEvent;
82 XClientMessageEvent xclient;
83 #endif
84 
85 #define xdnd_xfree(x) {if (x) { free (x); x = 0; }}
86 
87 #define dnd_version_at_least(a,b) ((a) <= (b))
88 
89 
90 static unsigned char dnd_copy_cursor_bits[] =
91 {
92   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
93   0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0xe8, 0x0f,
94   0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01,
95   0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
96   0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
97   0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
98   0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
99   0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
100     0x00, 0x00, 0x00, 0x00};
101 
102 static unsigned char dnd_copy_mask_bits[] =
103 {
104   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
105   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
106   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
107   0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
108   0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
109   0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
110   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
111   0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
112     0x00, 0x80, 0x01, 0x00};
113 
114 static unsigned char dnd_move_cursor_bits[] =
115 {
116   0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
117   0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
118   0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x04, 0x08, 0x02, 0x0c, 0x08,
119   0x02, 0x1c, 0x08, 0x02, 0x3c, 0x08, 0x02, 0x7c, 0x08, 0x02, 0xfc, 0x08,
120   0x02, 0xfc, 0x09, 0x02, 0xfc, 0x0b, 0x02, 0x7c, 0x08, 0xfe, 0x6d, 0x0f,
121   0x00, 0xc4, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01,
122     0x00, 0x00, 0x00};
123 
124 static unsigned char dnd_move_mask_bits[] =
125 {
126   0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0x07, 0x00, 0x1c,
127   0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c,
128   0x07, 0x00, 0x1c, 0x07, 0x06, 0x1c, 0x07, 0x0e, 0x1c, 0x07, 0x1e, 0x1c,
129   0x07, 0x3e, 0x1c, 0x07, 0x7e, 0x1c, 0x07, 0xfe, 0x1c, 0x07, 0xfe, 0x1d,
130   0x07, 0xfe, 0x1f, 0x07, 0xfe, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1e,
131   0xff, 0xef, 0x1f, 0x00, 0xe6, 0x01, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03,
132     0x00, 0x80, 0x01};
133 
134 static unsigned char dnd_link_cursor_bits[] =
135 {
136   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
137   0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0xe8, 0x0f,
138   0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x08, 0x01,
139   0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
140   0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
141   0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
142   0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
143   0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
144     0x00, 0x00, 0x00, 0x00};
145 
146 static unsigned char dnd_link_mask_bits[] =
147 {
148   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
149   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
150   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
151   0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
152   0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
153   0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
154   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
155   0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
156     0x00, 0x80, 0x01, 0x00};
157 
158 static unsigned char dnd_ask_cursor_bits[] =
159 {
160   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x88, 0x03,
161   0x02, 0x00, 0x48, 0x04, 0x02, 0x00, 0x08, 0x04, 0x02, 0x00, 0x08, 0x02,
162   0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x00,
163   0x02, 0x00, 0x08, 0x01, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
164   0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
165   0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
166   0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
167   0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
168     0x00, 0x00, 0x00, 0x00};
169 
170 static unsigned char dnd_ask_mask_bits[] =
171 {
172   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
173   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
174   0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
175   0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
176   0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
177   0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
178   0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
179   0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
180     0x00, 0x80, 0x01, 0x00};
181 
182 static DndCursor dnd_cursors[] =
183 {
184     {29, 25, 10, 10, dnd_copy_cursor_bits, dnd_copy_mask_bits, "XdndActionCopy", 0, 0, 0, 0},
185     {21, 25, 10, 10, dnd_move_cursor_bits, dnd_move_mask_bits, "XdndActionMove", 0, 0, 0, 0},
186     {29, 25, 10, 10, dnd_link_cursor_bits, dnd_link_mask_bits, "XdndActionLink", 0, 0, 0, 0},
187     {29, 25, 10, 10, dnd_ask_cursor_bits, dnd_ask_mask_bits, "XdndActionAsk", 0, 0, 0, 0},
188     {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
189 };
190 
xdnd_reset(DndClass * dnd)191 void xdnd_reset (DndClass * dnd)
192 {
193     dnd->stage = XDND_DROP_STAGE_IDLE;
194     dnd->dragging_version = 0;
195     dnd->internal_drag = 0;
196     dnd->want_position = 0;
197     dnd->ready_to_drop = 0;
198     dnd->will_accept = 0;
199     dnd->rectangle.x = dnd->rectangle.y = 0;
200     dnd->rectangle.width = dnd->rectangle.height = 0;
201     dnd->dropper_window = 0;
202     dnd->dragger_window = 0;
203     dnd->dragger_typelist = 0;
204     dnd->desired_type = 0;
205     dnd->time = 0;
206 }
207 
xdnd_init(DndClass * dnd,Display * display)208 void xdnd_init (DndClass * dnd, Display * display)
209 {
210     DndCursor *cursor;
211     XColor black, white;
212     memset (dnd, 0, sizeof (*dnd));
213 
214     dnd->display = display;
215     dnd->root_window = DefaultRootWindow (display);
216     dnd->version = XDND_VERSION;
217 
218     dnd->XdndAware = XInternAtom (dnd->display, "XdndAware", False);
219     dnd->XdndSelection = XInternAtom (dnd->display, "XdndSelection", False);
220     dnd->XdndEnter = XInternAtom (dnd->display, "XdndEnter", False);
221     dnd->XdndLeave = XInternAtom (dnd->display, "XdndLeave", False);
222     dnd->XdndPosition = XInternAtom (dnd->display, "XdndPosition", False);
223     dnd->XdndDrop = XInternAtom (dnd->display, "XdndDrop", False);
224     dnd->XdndFinished = XInternAtom (dnd->display, "XdndFinished", False);
225     dnd->XdndStatus = XInternAtom (dnd->display, "XdndStatus", False);
226     dnd->XdndActionCopy = XInternAtom (dnd->display, "XdndActionCopy", False);
227     dnd->XdndActionMove = XInternAtom (dnd->display, "XdndActionMove", False);
228     dnd->XdndActionLink = XInternAtom (dnd->display, "XdndActionLink", False);
229     dnd->XdndActionAsk = XInternAtom (dnd->display, "XdndActionAsk", False);
230     dnd->XdndActionPrivate = XInternAtom (dnd->display, "XdndActionPrivate", False);
231     dnd->XdndTypeList = XInternAtom (dnd->display, "XdndTypeList", False);
232     dnd->XdndActionList = XInternAtom (dnd->display, "XdndActionList", False);
233     dnd->XdndActionDescription = XInternAtom (dnd->display, "XdndActionDescription", False);
234 
235     dnd->Xdnd_NON_PROTOCOL_ATOM = XInternAtom (dnd->display, "JXSelectionWindowProperty", False);
236 
237     xdnd_reset (dnd);
238 
239     dnd->cursors = dnd_cursors;
240 
241     black.pixel = BlackPixel (dnd->display, DefaultScreen (dnd->display));
242     white.pixel = WhitePixel (dnd->display, DefaultScreen (dnd->display));
243 
244     XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &black);
245     XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &white);
246 
247     for (cursor = &dnd->cursors[0]; cursor->width; cursor++) {
248 	cursor->image_pixmap = XCreateBitmapFromData \
249 	    (dnd->display, dnd->root_window, (char *) cursor->image_data, cursor->width, cursor->height);
250 	cursor->mask_pixmap = XCreateBitmapFromData \
251 	    (dnd->display, dnd->root_window, (char *) cursor->mask_data, cursor->width, cursor->height);
252 	cursor->cursor = XCreatePixmapCursor (dnd->display, cursor->image_pixmap,
253 	      cursor->mask_pixmap, &black, &white, cursor->x, cursor->y);
254 	XFreePixmap (dnd->display, cursor->image_pixmap);
255 	XFreePixmap (dnd->display, cursor->mask_pixmap);
256 	cursor->action = XInternAtom (dnd->display, cursor->_action, False);
257     }
258 }
259 
xdnd_shut(DndClass * dnd)260 void xdnd_shut (DndClass * dnd)
261 {
262     DndCursor *cursor;
263     for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
264 	XFreeCursor (dnd->display, cursor->cursor);
265     memset (dnd, 0, sizeof (*dnd));
266     return;
267 }
268 
269 
270 /* typelist is a null terminated array */
array_length(Atom * a)271 static int array_length (Atom * a)
272 {
273     int n;
274     for (n = 0; a[n]; n++);
275     return n;
276 }
277 
xdnd_set_dnd_aware(DndClass * dnd,Window window,Atom * typelist)278 void xdnd_set_dnd_aware (DndClass * dnd, Window window, Atom * typelist)
279 {
280     XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char *) &dnd->version, 1);
281     if (typelist) {
282 	int n;
283 	n = array_length (typelist);
284 	if (n)
285 	    XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeAppend, (unsigned char *) typelist, n);
286     }
287 }
288 
xdnd_is_dnd_aware(DndClass * dnd,Window window,int * version,Atom * typelist)289 int xdnd_is_dnd_aware (DndClass * dnd, Window window, int *version, Atom * typelist)
290 {
291     Atom actual;
292     int format;
293     unsigned long count, remaining;
294     unsigned char *data = 0;
295     Atom *types, *t;
296     int result = 1;
297 
298     *version = 0;
299     XGetWindowProperty (dnd->display, window, dnd->XdndAware,
300 			0, 0x8000000L, False, XA_ATOM,
301 			&actual, &format,
302 			&count, &remaining, &data);
303 
304     if (actual != XA_ATOM || format != 32 || count == 0 || !data) {
305         DEBUGOUT(_LtDebug(__FILE__, NULL, "XGetWindowProperty failed in xdnd_is_dnd_aware - XdndAware = %ld", dnd->XdndAware));
306 
307 	if (data)
308 	    XFree (data);
309 	return 0;
310     }
311     types = (Atom *) data;
312     *version = dnd->version < types[0] ? dnd->version : types[0];	/* minimum */
313     DEBUGOUT(_LtDebug(__FILE__, NULL,"Using XDND version %d", *version));
314     if (count > 1) {
315 	result = 0;
316 	for (t = typelist; *t; t++) {
317 	    int j;
318 	    for (j = 1; j < count; j++) {
319 		if (types[j] == *t) {
320 		    result = 1;
321 		    break;
322 		}
323 	    }
324 	    if (result)
325 		break;
326 	}
327     }
328     XFree (data);
329     return result;
330 }
331 
xdnd_set_type_list(DndClass * dnd,Window window,Atom * typelist)332 void xdnd_set_type_list (DndClass * dnd, Window window, Atom * typelist)
333 {
334     int n;
335     n = array_length (typelist);
336     XChangeProperty (dnd->display, window, dnd->XdndTypeList, XA_ATOM, 32,
337 		     PropModeReplace, (unsigned char *) typelist, n);
338 }
339 
340 /* result must be free'd */
xdnd_get_type_list(DndClass * dnd,Window window,Atom ** typelist)341 void xdnd_get_type_list (DndClass * dnd, Window window, Atom ** typelist)
342 {
343     Atom type, *a;
344     int format, i;
345     unsigned long count, remaining;
346     unsigned char *data = NULL;
347 
348     *typelist = 0;
349 
350     XGetWindowProperty (dnd->display, window, dnd->XdndTypeList,
351 			0, 0x8000000L, False, XA_ATOM,
352 			&type, &format, &count, &remaining, &data);
353 
354     if (type != XA_ATOM || format != 32 || count == 0 || !data) {
355 	if (data)
356 	    XFree (data);
357         DEBUGOUT(_LtDebug(__FILE__, NULL, "XGetWindowProperty failed in xdnd_get_type_list - dnd->XdndTypeList = %ld", dnd->XdndTypeList));
358 	return;
359     }
360     *typelist = malloc ((count + 1) * sizeof (Atom));
361     a = (Atom *) data;
362     for (i = 0; i < count; i++)
363 	(*typelist)[i] = a[i];
364     (*typelist)[count] = 0;
365 
366     XFree (data);
367 }
368 
xdnd_get_three_types(DndClass * dnd,XEvent * xevent,Atom ** typelist)369 void xdnd_get_three_types (DndClass * dnd, XEvent * xevent, Atom ** typelist)
370 {
371     int i;
372     *typelist = malloc ((XDND_THREE + 1) * sizeof (Atom));
373     for (i = 0; i < XDND_THREE; i++)
374 	(*typelist)[i] = XDND_ENTER_TYPE (xevent, i);
375     (*typelist)[XDND_THREE] = 0;	/* although (*typelist)[1] or (*typelist)[2] may also be set to nill */
376 }
377 
378 /* result must be free'd */
concat_string_list(char ** t,int * bytes)379 static char *concat_string_list (char **t, int *bytes)
380 {
381     int l, n;
382     char *s;
383     for (l = n = 0;; n++) {
384 	if (!t[n])
385 	    break;
386 	if (!t[n][0])
387 	    break;
388 	l += strlen (t[n]) + 1;
389     }
390     s = malloc (l + 1);
391     for (l = n = 0;; n++) {
392 	if (!t[n])
393 	    break;
394 	if (!(t[n][0]))
395 	    break;
396 	strcpy (s + l, t[n]);
397 	l += strlen (t[n]) + 1;
398     }
399     *bytes = l;
400     s[l] = '\0';
401     return s;
402 }
403 
xdnd_set_actions(DndClass * dnd,Window window,Atom * actions,char ** descriptions)404 void xdnd_set_actions (DndClass * dnd, Window window, Atom * actions, char **descriptions)
405 {
406     int n, l;
407     char *s;
408     n = array_length (actions);
409 
410     XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_ATOM, 32,
411 		     PropModeReplace, (unsigned char *) actions, n);
412 
413     s = concat_string_list (descriptions, &l);
414     XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_STRING, 8,
415 		     PropModeReplace, (unsigned char *) s, l);
416     xdnd_xfree (s);
417 }
418 
419 /* returns 1 on error or no actions, otherwise result must be free'd
420    xdnd_get_actions (window, &actions, &descriptions);
421    free (actions); free (descriptions); */
xdnd_get_actions(DndClass * dnd,Window window,Atom ** actions,char *** descriptions)422 int xdnd_get_actions (DndClass * dnd, Window window, Atom ** actions, char ***descriptions)
423 {
424     Atom type, *a;
425     int format, i;
426     unsigned long count, dcount, remaining;
427     unsigned char *data = 0, *r;
428 
429     *actions = 0;
430     *descriptions = 0;
431     XGetWindowProperty (dnd->display, window, dnd->XdndActionList,
432 			0, 0x8000000L, False, XA_ATOM,
433 			&type, &format, &count, &remaining, &data);
434 
435     if (type != XA_ATOM || format != 32 || count == 0 || !data) {
436 	if (data)
437 	    XFree (data);
438 	return 1;
439     }
440     *actions = malloc ((count + 1) * sizeof (Atom));
441     a = (Atom *) data;
442     for (i = 0; i < count; i++)
443 	(*actions)[i] = a[i];
444     (*actions)[count] = 0;
445 
446     XFree (data);
447 
448     data = 0;
449     XGetWindowProperty (dnd->display, window, dnd->XdndActionDescription,
450 			0, 0x8000000L, False, XA_STRING, &type, &format,
451 			&dcount, &remaining, &data);
452 
453     if (type != XA_STRING || format != 8 || dcount == 0) {
454 	if (data)
455 	    XFree (data);
456 	*descriptions = malloc ((count + 1) * sizeof (char *));
457 	_XmWarning(NULL, "XGetWindowProperty no property or wrong format for action descriptions");
458 	for (i = 0; i < count; i++)
459 	    (*descriptions)[i] = "";
460 	(*descriptions)[count] = 0;
461     } else {
462 	int l;
463 	l = (count + 1) * sizeof (char *);
464 	*descriptions = malloc (l + dcount);
465 	memcpy (*descriptions + l, data, dcount);
466 	XFree (data);
467 	data = (unsigned char *) *descriptions;
468 	data += l;
469 	l = 0;
470 	for (i = 0, r = data;; r += l + 1, i++) {
471 	    l = strlen ((char *) r);
472 	    if (!l || i >= count)
473 		break;
474 	    (*descriptions)[i] = (char *) r;
475 	}
476 	for (; i < count; i++) {
477 	    (*descriptions)[i] = "";
478 	}
479 	(*descriptions)[count] = 0;
480     }
481     return 0;
482 }
483 
484 /* returns non-zero on cancel */
xdnd_choose_action_dialog(DndClass * dnd,Atom * actions,char ** descriptions,Atom * result)485 int xdnd_choose_action_dialog (DndClass * dnd, Atom * actions, char **descriptions, Atom * result)
486 {
487     if (!actions[0])
488 	return 1;
489     if (!dnd->action_choose_dialog) {	/* default to return the first action if no dialog set */
490 	*result = actions[0];
491 	return 0;
492     }
493     return (*dnd->action_choose_dialog) (dnd, descriptions, actions, result);
494 }
495 
xdnd_send_event(DndClass * dnd,Window window,XEvent * xevent)496 static void xdnd_send_event (DndClass * dnd, Window window, XEvent * xevent)
497 {
498     XSendEvent (dnd->display, window, 0, 0, xevent);
499 }
500 
xdnd_send_enter(DndClass * dnd,Window window,Window from,Atom * typelist)501 void xdnd_send_enter (DndClass * dnd, Window window, Window from, Atom * typelist)
502 {
503     XEvent xevent;
504     int n, i;
505     n = array_length (typelist);
506 
507     memset (&xevent, 0, sizeof (xevent));
508 
509     xevent.xany.type = ClientMessage;
510     xevent.xany.display = dnd->display;
511     xevent.xclient.window = window;
512     xevent.xclient.message_type = dnd->XdndEnter;
513     xevent.xclient.format = 32;
514 
515     XDND_ENTER_SOURCE_WIN (&xevent) = from;
516     XDND_ENTER_THREE_TYPES_SET (&xevent, n > XDND_THREE);
517     XDND_ENTER_VERSION_SET (&xevent, dnd->version);
518     for (i = 0; i < n && i < XDND_THREE; i++)
519 	XDND_ENTER_TYPE (&xevent, i) = typelist[i];
520     xdnd_send_event (dnd, window, &xevent);
521 }
522 
xdnd_send_position(DndClass * dnd,Window window,Window from,Atom action,int x,int y,unsigned long time)523 void xdnd_send_position (DndClass * dnd, Window window, Window from, Atom action, int x, int y, unsigned long time)
524 {
525     XEvent xevent;
526 
527     memset (&xevent, 0, sizeof (xevent));
528 
529     xevent.xany.type = ClientMessage;
530     xevent.xany.display = dnd->display;
531     xevent.xclient.window = window;
532     xevent.xclient.message_type = dnd->XdndPosition;
533     xevent.xclient.format = 32;
534 
535     XDND_POSITION_SOURCE_WIN (&xevent) = from;
536     XDND_POSITION_ROOT_SET (&xevent, x, y);
537     if (dnd_version_at_least (dnd->dragging_version, 1))
538 	XDND_POSITION_TIME (&xevent) = time;
539     if (dnd_version_at_least (dnd->dragging_version, 2))
540 	XDND_POSITION_ACTION (&xevent) = action;
541 
542     xdnd_send_event (dnd, window, &xevent);
543 }
544 
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)545 void xdnd_send_status (DndClass * dnd, Window window, Window from, int will_accept, \
546 	      int want_position, int x, int y, int w, int h, Atom action)
547 {
548     XEvent xevent;
549 
550     memset (&xevent, 0, sizeof (xevent));
551 
552     xevent.xany.type = ClientMessage;
553     xevent.xany.display = dnd->display;
554     xevent.xclient.window = window;
555     xevent.xclient.message_type = dnd->XdndStatus;
556     xevent.xclient.format = 32;
557 
558     XDND_STATUS_TARGET_WIN (&xevent) = from;
559     XDND_STATUS_WILL_ACCEPT_SET (&xevent, will_accept);
560     if (will_accept)
561 	XDND_STATUS_WANT_POSITION_SET (&xevent, want_position);
562     if (want_position)
563 	XDND_STATUS_RECT_SET (&xevent, x, y, w, h);
564     if (dnd_version_at_least (dnd->dragging_version, 2))
565 	if (will_accept)
566 	    XDND_STATUS_ACTION (&xevent) = action;
567 
568     xdnd_send_event (dnd, window, &xevent);
569 }
570 
xdnd_send_leave(DndClass * dnd,Window window,Window from)571 void xdnd_send_leave (DndClass * dnd, Window window, Window from)
572 {
573     XEvent xevent;
574 
575     memset (&xevent, 0, sizeof (xevent));
576 
577     xevent.xany.type = ClientMessage;
578     xevent.xany.display = dnd->display;
579     xevent.xclient.window = window;
580     xevent.xclient.message_type = dnd->XdndLeave;
581     xevent.xclient.format = 32;
582 
583     XDND_LEAVE_SOURCE_WIN (&xevent) = from;
584 
585     xdnd_send_event (dnd, window, &xevent);
586 }
587 
xdnd_send_drop(DndClass * dnd,Window window,Window from,unsigned long time)588 void xdnd_send_drop (DndClass * dnd, Window window, Window from, unsigned long time)
589 {
590     XEvent xevent;
591 
592     memset (&xevent, 0, sizeof (xevent));
593 
594     xevent.xany.type = ClientMessage;
595     xevent.xany.display = dnd->display;
596     xevent.xclient.window = window;
597     xevent.xclient.message_type = dnd->XdndDrop;
598     xevent.xclient.format = 32;
599 
600     XDND_DROP_SOURCE_WIN (&xevent) = from;
601     if (dnd_version_at_least (dnd->dragging_version, 1))
602 	XDND_DROP_TIME (&xevent) = time;
603 
604     xdnd_send_event (dnd, window, &xevent);
605 }
606 
607 /* error is not actually used, i think future versions of the protocol should return an error status
608    to the calling window with the XdndFinished client message */
xdnd_send_finished(DndClass * dnd,Window window,Window from,int error)609 void xdnd_send_finished (DndClass * dnd, Window window, Window from, int error)
610 {
611     XEvent xevent;
612 
613     memset (&xevent, 0, sizeof (xevent));
614 
615     xevent.xany.type = ClientMessage;
616     xevent.xany.display = dnd->display;
617     xevent.xclient.window = window;
618     xevent.xclient.message_type = dnd->XdndFinished;
619     xevent.xclient.format = 32;
620 
621     XDND_FINISHED_TARGET_WIN (&xevent) = from;
622 
623     xdnd_send_event (dnd, window, &xevent);
624 }
625 
626 /* returns non-zero on error - i.e. no selection owner set. Type is of course the mime type */
xdnd_convert_selection(DndClass * dnd,Window window,Window requester,Atom type)627 int xdnd_convert_selection (DndClass * dnd, Window window, Window requester, Atom type)
628 {
629     if (window != XGetSelectionOwner (dnd->display, dnd->XdndSelection)) {
630         DEBUGOUT(_LtDebug(__FILE__, NULL,"xdnd_convert_selection(): XGetSelectionOwner failed"));
631 	return 1;
632     }
633     XConvertSelection (dnd->display, dnd->XdndSelection, type,
634 		    dnd->Xdnd_NON_PROTOCOL_ATOM, requester, CurrentTime);
635     return 0;
636 }
637 
638 /* returns non-zero on error */
xdnd_set_selection_owner(DndClass * dnd,Window window,Atom type)639 int xdnd_set_selection_owner (DndClass * dnd, Window window, Atom type)
640 {
641     if (!XSetSelectionOwner (dnd->display, dnd->XdndSelection, window, CurrentTime)) {
642         DEBUGOUT(_LtDebug(__FILE__, NULL, "xdnd_set_selection_owner(): XSetSelectionOwner failed"));
643 	return 1;
644     }
645     return 0;
646 }
647 
xdnd_selection_send(DndClass * dnd,XSelectionRequestEvent * request,unsigned char * data,int length)648 void xdnd_selection_send (DndClass * dnd, XSelectionRequestEvent * request, unsigned char *data, int length)
649 {
650     XEvent xevent;
651     DEBUGOUT(_LtDebug(__FILE__, NULL, "      requestor = %ld", request->requestor));
652     DEBUGOUT(_LtDebug(__FILE__, NULL, "      property = %ld", request->property));
653     DEBUGOUT(_LtDebug(__FILE__, NULL, "      length = %d", length));
654     XChangeProperty (dnd->display, request->requestor, request->property,
655 		     request->target, 8, PropModeReplace, data, length);
656     xevent.xselection.type = SelectionNotify;
657     xevent.xselection.property = request->property;
658     xevent.xselection.display = request->display;
659     xevent.xselection.requestor = request->requestor;
660     xevent.xselection.selection = request->selection;
661     xevent.xselection.target = request->target;
662     xevent.xselection.time = request->time;
663     xdnd_send_event (dnd, request->requestor, &xevent);
664 }
665 
666 /* respond to a notification that a primary selection has been sent */
xdnd_get_selection(DndClass * dnd,Window from,Atom property,Window insert)667 int xdnd_get_selection (DndClass * dnd, Window from, Atom property, Window insert)
668 {
669     long read;
670     int error = 0;
671     unsigned long remaining;
672     if (!property)
673 	return 1;
674     read = 0;
675     do {
676 	unsigned char *s;
677 	Atom actual;
678 	int format;
679 	unsigned long count;
680 	if (XGetWindowProperty (dnd->display, insert, property, read / 4, 65536, 1,
681 				AnyPropertyType, &actual, &format,
682 				&count, &remaining,
683 				&s) != Success) {
684 	    XFree (s);
685 	    return 1;
686 	}
687 	read += count;
688 	if (dnd->widget_insert_drop && !error)
689 	    error = (*dnd->widget_insert_drop) (dnd, s, count, remaining, insert, from, actual);
690 	XFree (s);
691     } while (remaining);
692     return error;
693 }
694 
695 
outside_rectangle(int x,int y,XRectangle * r)696 int outside_rectangle (int x, int y, XRectangle * r)
697 {
698     return (x < r->x || y < r->y || x >= r->x + r->width || y >= r->y + r->height);
699 }
700 
701 /* avoids linking with the maths library */
xdnd_sqrt(float x)702 static float xdnd_sqrt (float x)
703 {
704     float last_ans, ans = 2, a;
705     if (x <= 0.0)
706 	return 0.0;
707     do {
708 	last_ans = ans;
709 	ans = (ans + x / ans) / 2;
710 	a = (ans - last_ans) / ans;
711 	if (a < 0.0)
712 	    a = (-a);
713     } while (a > 0.001);
714     return ans;
715 }
716 
717 /* returns action on success, 0 otherwise */
xdnd_drag(DndClass * dnd,Window from,Atom action,Atom * typelist)718 Atom xdnd_drag (DndClass * dnd, Window from, Atom action, Atom * typelist)
719 {
720     XEvent xevent, xevent_temp;
721     Window over_window = 0, last_window = 0;
722     int n;
723     DndCursor *cursor;
724     float x_mouse, y_mouse;
725     int result = 0, dnd_aware;
726 
727     if (!typelist)
728 	_XmWarning(NULL, "xdnd_drag() called with typelist = 0");
729 
730 /* first wait until the mouse moves more than five pixels */
731     do {
732 	XNextEvent (dnd->display, &xevent);
733 	if (xevent.type == ButtonRelease) {
734             DEBUGOUT(_LtDebug(__FILE__, NULL, "button release - no motion"));
735 	    XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
736 	    return 0;
737 	}
738     } while (xevent.type != MotionNotify);
739 
740     x_mouse = (float) xevent.xmotion.x_root;
741     y_mouse = (float) xevent.xmotion.y_root;
742 
743     if (!dnd->drag_threshold)
744 	dnd->drag_threshold = 4.0;
745     for (;;) {
746 	XNextEvent (dnd->display, &xevent);
747 	if (xevent.type == MotionNotify)
748 	    if (xdnd_sqrt ((x_mouse - xevent.xmotion.x_root) * (x_mouse - xevent.xmotion.x_root) +
749 			   (y_mouse - xevent.xmotion.y_root) * (y_mouse - xevent.xmotion.y_root)) > dnd->drag_threshold)
750 		break;
751 	if (xevent.type == ButtonRelease) {
752 	    XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
753 	    return 0;
754 	}
755     }
756 
757     DEBUGOUT(_LtDebug(__FILE__, NULL, "moved 5 pixels - going to drag"));
758 
759     n = array_length (typelist);
760     if (n > XDND_THREE)
761 	xdnd_set_type_list (dnd, from, typelist);
762 
763     xdnd_reset (dnd);
764 
765     dnd->stage = XDND_DRAG_STAGE_DRAGGING;
766 
767     for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
768 	if (cursor->action == action)
769 	    break;
770     if (!cursor->width)
771 	cursor = &dnd->cursors[0];
772 
773 /* the mouse has been dragged a little, so this is a drag proper */
774     if (XGrabPointer (dnd->display, dnd->root_window, False,
775 		      ButtonMotionMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
776 		      GrabModeAsync, GrabModeAsync, None,
777 		      cursor->cursor, CurrentTime) != GrabSuccess)
778         DEBUGOUT(_LtDebug(__FILE__, NULL, "Unable to grab pointer"));
779 
780     while (xevent.xany.type != ButtonRelease) {
781 	XAllowEvents (dnd->display, SyncPointer, CurrentTime);
782 	XNextEvent (dnd->display, &xevent);
783 	switch (xevent.type) {
784 	case Expose:
785 	    if (dnd->handle_expose_events)
786 		(*dnd->handle_expose_events) (dnd, &xevent);
787 	    break;
788 	case EnterNotify:
789 /* this event is not actually reported, so we find out by ourselves from motion events */
790 	    break;
791 	case LeaveNotify:
792 /* this event is not actually reported, so we find out by ourselves from motion events */
793 	    break;
794 	case ButtonRelease:
795 /* done, but must send a leave event */
796             DEBUGOUT(_LtDebug(__FILE__, NULL, "ButtonRelease - exiting event loop"));
797 	    break;
798 	case MotionNotify:
799 	    memcpy (&xevent_temp, &xevent, sizeof (xevent));
800 	    if (!xevent.xmotion.subwindow) {
801 		xevent.xmotion.subwindow = xevent.xmotion.window;
802 	    } else {
803 		Window root_return, child_return;
804 		unsigned int mask_return;
805 		while (XQueryPointer (dnd->display, xevent.xmotion.subwindow, &root_return, &child_return,
806 				      &xevent.xmotion.x_root, &xevent.xmotion.y_root, &xevent.xmotion.x,
807 				      &xevent.xmotion.y, &mask_return)) {
808 		    if (!child_return)
809 			goto found_descendent;
810 		    xevent.xmotion.subwindow = child_return;
811 		}
812 		break;
813 	    }
814 	  found_descendent:
815 /* last_window is just for debug purposes */
816 	    if (last_window != xevent.xmotion.subwindow) {
817                 DEBUGOUT(_LtDebug(__FILE__, NULL, "window crossing to %ld", xevent.xmotion.subwindow));
818                 DEBUGOUT(_LtDebug(__FILE__, NULL, "  current window is %ld", over_window));
819 	    }
820 	    dnd_aware = 0;
821 /* is the new window dnd aware? if not stay in the old window */
822 	    if (over_window != xevent.xmotion.subwindow &&
823 		last_window != xevent.xmotion.subwindow &&
824 		(
825 		    (dnd_aware = xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist)) ||
826 		    (dnd->options & XDND_OPTION_NO_HYSTERESIS)
827 		)) {
828 /* leaving window we were over */
829 		xevent.xany.window = over_window;
830 		if (over_window) {
831 		    if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
832                         DEBUGOUT(_LtDebug(__FILE__, NULL, "got leave at right stage"));
833 			dnd->stage = XDND_DRAG_STAGE_DRAGGING;
834 			if (dnd->internal_drag) {
835                             DEBUGOUT(_LtDebug(__FILE__, NULL, "  our own widget"));
836 			    if (dnd->widget_apply_leave)
837 				(*dnd->widget_apply_leave) (dnd, xevent.xany.window);
838 			} else {
839                             DEBUGOUT(_LtDebug(__FILE__, NULL, "  not our widget - sending XdndLeave"));
840 			    xdnd_send_leave (dnd, xevent.xany.window, from);
841 			}
842 			dnd->internal_drag = 0;
843 			dnd->dropper_window = 0;
844 			dnd->ready_to_drop = 0;
845 		    } else {
846 			DEBUGOUT(_LtDebug(__FILE__, NULL, "got leave at wrong stage - ignoring"));
847 		    }
848 		}
849 /* entering window we are currently over */
850 		over_window = xevent.xmotion.subwindow;
851 		if (dnd_aware) {
852 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "  is dnd aware"));
853 		    dnd->stage = XDND_DRAG_STAGE_ENTERED;
854 		    if (dnd->widget_exists && (*dnd->widget_exists) (dnd, over_window))
855 			dnd->internal_drag = 1;
856 		    if (dnd->internal_drag) {
857 			DEBUGOUT(_LtDebug(__FILE__, NULL, "    our own widget"));
858 		    } else {
859 			DEBUGOUT(_LtDebug(__FILE__, NULL, "    not our widget - sending XdndEnter"));
860 			xdnd_send_enter (dnd, over_window, from, typelist);
861 		    }
862 		    dnd->want_position = 1;
863 		    dnd->ready_to_drop = 0;
864 		    dnd->rectangle.width = dnd->rectangle.height = 0;
865 		    dnd->dropper_window = over_window;
866 /* we want an additional motion event in case the pointer enters and then stops */
867 		    XSendEvent (dnd->display, from, 0, ButtonMotionMask, &xevent_temp);
868 		    XSync (dnd->display, 0);
869 		}
870 /* we are now officially in a new window */
871 	    } else {
872 /* got here, so we are just moving `inside' the same window */
873 		if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
874 		    dnd->supported_action = dnd->XdndActionCopy;
875 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "got motion at right stage"));
876 		    dnd->x = xevent.xmotion.x_root;
877 		    dnd->y = xevent.xmotion.y_root;
878 		    if (dnd->want_position && outside_rectangle (dnd->x, dnd->y, &dnd->rectangle)) {
879 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  want position and outside rectangle"));
880 			if (dnd->internal_drag) {
881 			    DEBUGOUT(_LtDebug(__FILE__, NULL, "    our own widget"));
882 			    dnd->ready_to_drop = (*dnd->widget_apply_position) (dnd, over_window, from,
883 										action, dnd->x, dnd->y, xevent.xmotion.time, typelist,
884 										&dnd->want_position, &dnd->supported_action, &dnd->desired_type, &dnd->rectangle);
885 			    /* if not ready, keep sending positions, this check is repeated below for XdndStatus from external widgets */
886 			    if (!dnd->ready_to_drop) {
887 				dnd->want_position = 1;
888 				dnd->rectangle.width = dnd->rectangle.height = 0;
889 			    }
890 			    DEBUGOUT(_LtDebug(__FILE__, NULL, "      return action=%ld", dnd->supported_action));
891 			} else {
892 			    DEBUGOUT(_LtDebug(__FILE__, NULL, "    not our own widget - sending XdndPosition"));
893 			    xdnd_send_position (dnd, over_window, from, action, dnd->x, dnd->y, xevent.xmotion.time);
894 			}
895 		    } else {
896 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  doesn't want position or inside rectangle"));
897 			DEBUGOUT(_LtDebug(__FILE__, NULL, "    rectangle = (x=%d, y=%d, w=%d, h=%d), (x,y)=(%d,%d) want_position=%d\n", dnd->rectangle.x, dnd->rectangle.y, dnd->rectangle.width, dnd->rectangle.height, dnd->x, dnd->y, dnd->want_position));
898 		    }
899 		}
900 	    }
901 	    last_window = xevent.xmotion.subwindow;
902 	    break;
903 	case ClientMessage:
904 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "ClientMessage recieved"));
905 	    if (xevent.xclient.message_type == dnd->XdndStatus && !dnd->internal_drag) {
906 		DEBUGOUT(_LtDebug(__FILE__, NULL, "  XdndStatus recieved"));
907 		if (dnd->stage == XDND_DRAG_STAGE_ENTERED && XDND_STATUS_TARGET_WIN (&xevent) == dnd->dropper_window) {
908 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "    XdndStatus stage correct, dropper window correct"));
909 		    dnd->want_position = XDND_STATUS_WANT_POSITION (&xevent);
910 		    dnd->ready_to_drop = XDND_STATUS_WILL_ACCEPT (&xevent);
911 		    dnd->rectangle.x = XDND_STATUS_RECT_X (&xevent);
912 		    dnd->rectangle.y = XDND_STATUS_RECT_Y (&xevent);
913 		    dnd->rectangle.width = XDND_STATUS_RECT_WIDTH (&xevent);
914 		    dnd->rectangle.height = XDND_STATUS_RECT_HEIGHT (&xevent);
915 		    dnd->supported_action = dnd->XdndActionCopy;
916 		    if (dnd_version_at_least (dnd->dragging_version, 2))
917 			dnd->supported_action = XDND_STATUS_ACTION (&xevent);
918 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "      return action=%ld", dnd->supported_action));
919 		    /* if not ready, keep sending positions, this check is repeated above for internal widgets */
920 		    if (!dnd->ready_to_drop) {
921 			dnd->want_position = 1;
922 			dnd->rectangle.width = dnd->rectangle.height = 0;
923 		    }
924 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "      rectangle = (x=%d, y=%d, w=%d, h=%d), want_position=%d\n", dnd->rectangle.x, dnd->rectangle.y, dnd->rectangle.width, dnd->rectangle.height, dnd->want_position));
925 		} else {
926 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "    XdndStatus stage incorrect or dropper window incorrect"));
927 		}
928 	    }
929 	    break;
930 	case SelectionRequest:{
931 /* the target widget MAY request data, so wait for SelectionRequest */
932 		int length = 0;
933 		unsigned char *data = 0;
934 		DEBUGOUT(_LtDebug(__FILE__, NULL, "SelectionRequest - getting widget data"));
935 
936 		(*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
937 		if (data) {
938 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "  sending selection"));
939 		    xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
940 		    xdnd_xfree (data);
941 		}
942 	    }
943 	    break;
944 	}
945     }
946 
947     if (dnd->ready_to_drop) {
948 	Time time;
949 	DEBUGOUT(_LtDebug(__FILE__, NULL, "ready_to_drop - sending XdndDrop"));
950 	time = xevent.xbutton.time;
951 	if (dnd->internal_drag) {
952 /* we are dealing with our own widget, no need to send drop events, just put the data straight */
953 	    int length = 0;
954 	    unsigned char *data = 0;
955 	    if (dnd->widget_insert_drop) {
956 		(*dnd->widget_get_data) (dnd, from, &data, &length, dnd->desired_type);
957 		if (data) {
958 		    if (!(*dnd->widget_insert_drop) (dnd, data, length, 0, dnd->dropper_window, from, dnd->desired_type)) {
959 			result = dnd->supported_action;		/* success - so return action to caller */
960 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  inserted data into widget - success"));
961 		    } else {
962 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  inserted data into widget - failed"));
963 		    }
964 		    xdnd_xfree (data);
965 		} else {
966 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "  got data from widget, but data is null"));
967 		}
968 	    }
969 	} else {
970 	    xdnd_set_selection_owner (dnd, from, dnd->desired_type);
971 	    xdnd_send_drop (dnd, dnd->dropper_window, from, time);
972 	}
973 	if (!dnd->internal_drag)
974 	    for (;;) {
975 		XAllowEvents (dnd->display, SyncPointer, CurrentTime);
976 		XNextEvent (dnd->display, &xevent);
977 		if (xevent.type == ClientMessage && xevent.xclient.message_type == dnd->XdndFinished) {
978 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "XdndFinished"));
979 		    if (XDND_FINISHED_TARGET_WIN (&xevent) == dnd->dropper_window) {
980 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  source correct - exiting event loop, action=%ld", dnd->supported_action));
981 			result = dnd->supported_action;		/* success - so return action to caller */
982 			break;
983 		    }
984 		} else if (xevent.type == Expose) {
985 		    if (dnd->handle_expose_events)
986 			(*dnd->handle_expose_events) (dnd, &xevent);
987 		} else if (xevent.type == MotionNotify) {
988 		    if (xevent.xmotion.time > time + (dnd->time_out ? dnd->time_out * 1000 : 10000)) {	/* allow a ten second timeout as default */
989 			DEBUGOUT(_LtDebug(__FILE__, NULL, "timeout - exiting event loop"));
990 			break;
991 		    }
992 		} else if (xevent.type == SelectionRequest && xevent.xselectionrequest.selection == dnd->XdndSelection) {
993 /* the target widget is going to request data, so check for SelectionRequest events */
994 		    int length = 0;
995 		    unsigned char *data = 0;
996 
997 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "SelectionRequest - getting widget data"));
998 		    (*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
999 		    if (data) {
1000 			DEBUGOUT(_LtDebug(__FILE__, NULL, "  sending selection"));
1001 			xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
1002 			xdnd_xfree (data);
1003 		    }
1004 /* don't wait for a XdndFinished event */
1005 		    if (!dnd_version_at_least (dnd->dragging_version, 2))
1006 			break;
1007 		}
1008 	    }
1009     } else {
1010 	DEBUGOUT(_LtDebug(__FILE__, NULL, "not ready_to_drop - ungrabbing pointer"));
1011     }
1012     XUngrabPointer (dnd->display, CurrentTime);
1013     xdnd_reset (dnd);
1014     return result;
1015 }
1016 
1017 /* returns non-zero if event is handled */
xdnd_handle_drop_events(DndClass * dnd,XEvent * xevent)1018 int xdnd_handle_drop_events (DndClass * dnd, XEvent * xevent)
1019 {
1020     int result = 0;
1021     if (xevent->type == SelectionNotify) {
1022 	DEBUGOUT(_LtDebug(__FILE__, NULL, "got SelectionNotify"));
1023 	if (xevent->xselection.property == dnd->Xdnd_NON_PROTOCOL_ATOM && dnd->stage == XDND_DROP_STAGE_CONVERTING) {
1024 	    int error;
1025 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  property is Xdnd_NON_PROTOCOL_ATOM - getting selection"));
1026 	    error = xdnd_get_selection (dnd, dnd->dragger_window, xevent->xselection.property, xevent->xany.window);
1027 /* error is not actually used, i think future versions of the protocol maybe should return
1028    an error status to the calling window with the XdndFinished client message */
1029 	    if (dnd_version_at_least (dnd->dragging_version, 2))
1030 		xdnd_send_finished (dnd, dnd->dragger_window, dnd->dropper_window, error);
1031 	    xdnd_xfree (dnd->dragger_typelist);
1032 	    xdnd_reset (dnd);
1033 	    dnd->stage = XDND_DROP_STAGE_IDLE;
1034 	    result = 1;
1035 	} else {
1036 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  property is not Xdnd_NON_PROTOCOL_ATOM - ignoring"));
1037 	}
1038     } else if (xevent->type == ClientMessage) {
1039 	DEBUGOUT(_LtDebug(__FILE__, NULL, "got ClientMessage"));
1040 	if (xevent->xclient.message_type == dnd->XdndEnter) {
1041 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  message_type is XdndEnter"));
1042 	    xdnd_reset (dnd);
1043 	    dnd->dragger_window = XDND_ENTER_SOURCE_WIN (xevent);
1044 	    dnd->dropper_window = xevent->xany.window;
1045 	    xdnd_xfree (dnd->dragger_typelist);
1046 	    if (XDND_ENTER_THREE_TYPES (xevent)) {
1047 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    three types only"));
1048 		xdnd_get_three_types (dnd, xevent, &dnd->dragger_typelist);
1049 	    } else {
1050 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    more than three types - getting list"));
1051 		xdnd_get_type_list (dnd, dnd->dragger_window, &dnd->dragger_typelist);
1052 	    }
1053 	    if (dnd->dragger_typelist)
1054 		dnd->stage = XDND_DROP_STAGE_ENTERED;
1055 	    else
1056 		DEBUGOUT(_LtDebug(__FILE__, NULL, "      typelist returned as zero!"));
1057 	    dnd->dragging_version = XDND_ENTER_VERSION (xevent);
1058 	    result = 1;
1059 	} else if (xevent->xclient.message_type == dnd->XdndLeave) {
1060 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  message_type is XdndLeave"));
1061 	    if (dnd->dragger_window == XDND_LEAVE_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
1062 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    leaving"));
1063 		if (dnd->widget_apply_leave)
1064 		    (*dnd->widget_apply_leave) (dnd, xevent->xany.window);
1065 		dnd->stage = XDND_DROP_STAGE_IDLE;
1066 		xdnd_xfree (dnd->dragger_typelist);
1067 		result = 1;
1068 	    } else {
1069 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    wrong stage or from wrong window"));
1070 	    }
1071 	} else if (xevent->xclient.message_type == dnd->XdndPosition) {
1072 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  message_type is XdndPosition"));
1073 	    if (dnd->dragger_window == XDND_POSITION_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
1074 		int want_position;
1075 		Atom action;
1076 		XRectangle rectangle;
1077 		action = dnd->XdndActionCopy;
1078 		dnd->supported_action = dnd->XdndActionCopy;
1079 		dnd->x = XDND_POSITION_ROOT_X (xevent);
1080 		dnd->y = XDND_POSITION_ROOT_Y (xevent);
1081 		dnd->time = CurrentTime;
1082 		if (dnd_version_at_least (dnd->dragging_version, 1))
1083 		    dnd->time = XDND_POSITION_TIME (xevent);
1084 		if (dnd_version_at_least (dnd->dragging_version, 1))
1085 		    action = XDND_POSITION_ACTION (xevent);
1086 		dnd->will_accept = (*dnd->widget_apply_position) (dnd, xevent->xany.window, dnd->dragger_window,
1087 		action, dnd->x, dnd->y, dnd->time, dnd->dragger_typelist,
1088 								  &want_position, &dnd->supported_action, &dnd->desired_type, &rectangle);
1089 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    will accept = %d", dnd->will_accept));
1090 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    sending status"));
1091 		DEBUGOUT(_LtDebug(__FILE__, NULL, "      action=%ld, rectangle = (x=%d, y=%d, w=%d, h=%d), (x,y)=(%d,%d) want_position=%d\n", dnd->supported_action, rectangle.x, rectangle.y, rectangle.width, rectangle.height, dnd->x, dnd->y, want_position));
1092 		xdnd_send_status (dnd, dnd->dragger_window, xevent->xany.window, dnd->will_accept,
1093 				  want_position, rectangle.x, rectangle.y, rectangle.width, rectangle.height, dnd->supported_action);
1094 		result = 1;
1095 	    } else {
1096 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    wrong stage or from wrong window"));
1097 	    }
1098 	} else if (xevent->xclient.message_type == dnd->XdndDrop) {
1099 	    DEBUGOUT(_LtDebug(__FILE__, NULL, "  message_type is XdndDrop"));
1100 	    if (dnd->dragger_window == XDND_DROP_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
1101 		dnd->time = CurrentTime;
1102 		if (dnd_version_at_least (dnd->dragging_version, 1))
1103 		    dnd->time = XDND_DROP_TIME (xevent);
1104 		if (dnd->will_accept) {
1105 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "    will_accept is true - converting selectiong"));
1106 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "      my window is %ld", dnd->dropper_window));
1107 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "        source window is %ld", dnd->dragger_window));
1108 		    xdnd_convert_selection (dnd, dnd->dragger_window, dnd->dropper_window, dnd->desired_type);
1109 		    dnd->stage = XDND_DROP_STAGE_CONVERTING;
1110 		} else {
1111 		    DEBUGOUT(_LtDebug(__FILE__, NULL, "    will_accept is false - sending finished"));
1112 		    if (dnd_version_at_least (dnd->dragging_version, 1))
1113 			xdnd_send_finished (dnd, dnd->dragger_window, xevent->xany.window, 1);
1114 		    xdnd_xfree (dnd->dragger_typelist);
1115 		    xdnd_reset (dnd);
1116 		    dnd->stage = XDND_DROP_STAGE_IDLE;
1117 		}
1118 		result = 1;
1119 	    } else {
1120 		DEBUGOUT(_LtDebug(__FILE__, NULL, "    wrong stage or from wrong window"));
1121 	    }
1122 	}
1123     }
1124     return result;
1125 }
1126 
1127 /*
1128    Following here is a sample implementation: Suppose we want a window
1129    to recieve drops, but do not want to be concerned with setting up all
1130    the DndClass methods. All we then do is call xdnd_get_drop() whenever a
1131    ClientMessage is recieved. If the message has nothing to do with XDND,
1132    xdnd_get_drop quickly returns 0. If it is a XdndEnter message, then
1133    xdnd_get_drop enters its own XNextEvent loop and handles all XDND
1134    protocol messages internally, returning the action requested.
1135 
1136    You should pass a desired typelist and actionlist to xdnd_get_type.
1137    These must be null terminated arrays of atoms, or a null pointer
1138    if you would like any action or type to be accepted. If typelist
1139    is null then the first type of the dragging widgets typelist will
1140    be the one used. If actionlist is null, then only XdndActionCopy will
1141    be accepted.
1142 
1143    The result is stored in *data, length, type, x and y.
1144    *data must be free'd.
1145  */
1146 
1147 struct xdnd_get_drop_info {
1148     unsigned char *drop_data;
1149     int drop_data_length;
1150     int x, y;
1151     Atom return_type;
1152     Atom return_action;
1153     Atom *typelist;
1154     Atom *actionlist;
1155 };
1156 
widget_insert_drop(DndClass * dnd,unsigned char * data,int length,int remaining,Window into,Window from,Atom type)1157 static int widget_insert_drop (DndClass * dnd, unsigned char *data, int length, int remaining, Window into, Window from, Atom type)
1158 {
1159     struct xdnd_get_drop_info *i;
1160     i = (struct xdnd_get_drop_info *) dnd->user_hook1;
1161     if (!i->drop_data) {
1162 	i->drop_data = malloc (length);
1163 	if (!i->drop_data)
1164 	    return 1;
1165 	memcpy (i->drop_data, data, length);
1166 	i->drop_data_length = length;
1167     } else {
1168 	unsigned char *t;
1169 	t = malloc (i->drop_data_length + length);
1170 	if (!t) {
1171 	    free (i->drop_data);
1172 	    i->drop_data = 0;
1173 	    return 1;
1174 	}
1175 	memcpy (t, i->drop_data, i->drop_data_length);
1176 	memcpy (t + i->drop_data_length, data, length);
1177 	free (i->drop_data);
1178 	i->drop_data = t;
1179 	i->drop_data_length += length;
1180     }
1181     return 0;
1182 }
1183 
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)1184 static int widget_apply_position (DndClass * dnd, Window widgets_window, Window from,
1185 		      Atom action, int x, int y, Time t, Atom * typelist,
1186  int *want_position, Atom * supported_action_return, Atom * desired_type,
1187 				  XRectangle * rectangle)
1188 {
1189     int i, j;
1190     struct xdnd_get_drop_info *info;
1191     Atom *dropper_typelist, supported_type = 0;
1192     Atom *supported_actions, supported_action = 0;
1193 
1194     info = (struct xdnd_get_drop_info *) dnd->user_hook1;
1195     dropper_typelist = info->typelist;
1196     supported_actions = info->actionlist;
1197 
1198     if (dropper_typelist) {
1199 /* find a correlation: */
1200 	for (j = 0; dropper_typelist[j]; j++) {
1201 	    for (i = 0; typelist[i]; i++) {
1202 		if (typelist[i] == dropper_typelist[j]) {
1203 		    supported_type = typelist[i];
1204 		    break;
1205 		}
1206 	    }
1207 	    if (supported_type)
1208 		break;
1209 	}
1210     } else {
1211 /* user did not specify, so return first type */
1212 	supported_type = typelist[0];
1213     }
1214 /* not supported, so return false */
1215     if (!supported_type)
1216 	return 0;
1217 
1218     if (supported_actions) {
1219 	for (j = 0; supported_actions[j]; j++) {
1220 	    if (action == supported_actions[j]) {
1221 		supported_action = action;
1222 		break;
1223 	    }
1224 	}
1225     } else {
1226 /* user did not specify */
1227 	if (action == dnd->XdndActionCopy)
1228 	    supported_action = action;
1229     }
1230     if (!supported_action)
1231 	return 0;
1232 
1233     *want_position = 1;
1234     rectangle->x = rectangle->y = 0;
1235     rectangle->width = rectangle->height = 0;
1236 
1237     info->return_action = *supported_action_return = supported_action;
1238     info->return_type = *desired_type = supported_type;
1239     info->x = x;
1240     info->y = y;
1241 
1242     return 1;
1243 }
1244 
xdnd_get_drop(Display * display,XEvent * xevent,Atom * typelist,Atom * actionlist,unsigned char ** data,int * length,Atom * type,int * x,int * y)1245 Atom xdnd_get_drop (Display * display, XEvent * xevent, Atom * typelist, Atom * actionlist,
1246 	  unsigned char **data, int *length, Atom * type, int *x, int *y)
1247 {
1248     Atom action = 0;
1249     static int initialised = 0;
1250     static DndClass dnd;
1251     if (!initialised) {
1252 	xdnd_init (&dnd, display);
1253 	initialised = 1;
1254     }
1255     if (xevent->type != ClientMessage || xevent->xclient.message_type != dnd.XdndEnter) {
1256 	return 0;
1257     } else {
1258 	struct xdnd_get_drop_info i;
1259 
1260 /* setup user structure */
1261 	memset (&i, 0, sizeof (i));
1262 	i.typelist = actionlist;
1263 	i.typelist = typelist;
1264 	dnd.user_hook1 = &i;
1265 
1266 /* setup methods */
1267 	dnd.widget_insert_drop = widget_insert_drop;
1268 	dnd.widget_apply_position = widget_apply_position;
1269 
1270 /* main loop */
1271 	for (;;) {
1272 	    xdnd_handle_drop_events (&dnd, xevent);
1273 	    if (dnd.stage == XDND_DROP_STAGE_IDLE)
1274 		break;
1275 	    XNextEvent (dnd.display, xevent);
1276 	}
1277 
1278 /* return results */
1279 	if (i.drop_data) {
1280 	    *length = i.drop_data_length;
1281 	    *data = i.drop_data;
1282 	    action = i.return_action;
1283 	    *type = i.return_type;
1284 	    *x = i.x;
1285 	    *y = i.y;
1286 	}
1287     }
1288     return action;
1289 }
1290 
1291 
1292